Merge branch '7.0' into 8.0

Conflicts:
	.travis.yml
	__unported__/account_advanced_reconcile/res_config.py
	__unported__/account_advanced_reconcile/res_config_view.xml
	__unported__/account_statement_base_completion/__openerp__.py
	__unported__/account_statement_base_completion/i18n/fr.po
	account_advanced_reconcile/res_config.py
	account_advanced_reconcile/res_config_view.xml
	account_easy_reconcile/res_config.py
	account_easy_reconcile/res_config_view.xml
	account_invoice_reference/__openerp__.py
	account_invoice_reference/account_move.py
	statement_voucher_killer/__openerp__.py
	statement_voucher_killer/voucher.py
This commit is contained in:
Leonardo Pistone
2015-01-13 14:47:22 +01:00
68 changed files with 1174 additions and 348 deletions

View File

@@ -3,6 +3,7 @@ include =
*/bank-statement-reconcile/* */bank-statement-reconcile/*
omit = omit =
*/test/*
*/tests/* */tests/*
*__init__.py *__init__.py

View File

@@ -20,7 +20,6 @@
# #
############################################################################## ##############################################################################
import easy_reconcile from . import easy_reconcile
import base_advanced_reconciliation from . import base_advanced_reconciliation
import advanced_reconciliation from . import advanced_reconciliation
import res_config # noqa

View File

@@ -64,8 +64,8 @@ The base class to find the reconciliations is built to be as efficient as
possible. possible.
So basically, if you have an invoice with 3 payments (one per month), the first So basically, if you have an invoice with 3 payments (one per month), the first
month, it will partial reconcile the debit move line with the first payment, the month, it will partial reconcile the debit move line with the first payment,
second month, it will partial reconcile the debit move line with 2 first the second month, it will partial reconcile the debit move line with 2 first
payments, the third month, it will make the full reconciliation. payments, the third month, it will make the full reconciliation.
This module is perfectly adapted for E-Commerce business where a big volume of This module is perfectly adapted for E-Commerce business where a big volume of
@@ -75,7 +75,7 @@ many offices.
""", """,
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',
'data': ['easy_reconcile_view.xml', 'data': ['easy_reconcile_view.xml',
'res_config_view.xml'], ],
'test': [], 'test': [],
'images': [], 'images': [],
'installable': False, 'installable': False,

View File

@@ -23,10 +23,8 @@ import logging
from itertools import product from itertools import product
from openerp.osv import orm from openerp.osv import orm
from openerp import pooler
from openerp.tools.translate import _ from openerp.tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -188,7 +186,8 @@ class easy_reconcile_advanced(orm.AbstractModel):
If all the matchers match for a move line and an opposite move line, If all the matchers match for a move line and an opposite move line,
they are candidate for a reconciliation. they are candidate for a reconciliation.
""" """
opp_matchers = self._opposite_matchers(cr, uid, rec, opposite_move_line, opp_matchers = self._opposite_matchers(cr, uid, rec,
opposite_move_line,
context=context) context=context)
for matcher in matchers: for matcher in matchers:
try: try:
@@ -222,29 +221,10 @@ class easy_reconcile_advanced(orm.AbstractModel):
cr, uid, rec, move_line, op, matchers, context=context)] cr, uid, rec, move_line, op, matchers, context=context)]
def _action_rec(self, cr, uid, rec, context=None): def _action_rec(self, cr, uid, rec, context=None):
# we use a new cursor to be able to commit the reconciliation credit_lines = self._query_credit(cr, uid, rec, context=context)
# often. We have to create it here and not later to avoid problems debit_lines = self._query_debit(cr, uid, rec, context=context)
# where the new cursor sees the lines as reconciles but the old one result = self._rec_auto_lines_advanced(
# does not. cr, uid, rec, credit_lines, debit_lines, context=context)
if context is None:
context = {}
ctx = context.copy()
ctx['commit_every'] = (
rec.journal_id.company_id.reconciliation_commit_every
)
if ctx['commit_every']:
new_cr = pooler.get_db(cr.dbname).cursor()
else:
new_cr = cr
try:
credit_lines = self._query_credit(new_cr, uid, rec, context=ctx)
debit_lines = self._query_debit(new_cr, uid, rec, context=ctx)
result = self._rec_auto_lines_advanced(
new_cr, uid, rec, credit_lines, debit_lines, context=ctx)
finally:
if ctx['commit_every']:
new_cr.commit()
new_cr.close()
return result return result
def _skip_line(self, cr, uid, rec, move_line, context=None): def _skip_line(self, cr, uid, rec, move_line, context=None):

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Matthieu Dietrich
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import easy_reconcile
from . import advanced_reconciliation

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Matthieu Dietrich
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{'name': 'Advanced Reconcile Bank Statement',
'description': """
Advanced reconciliation method for the module account_advanced_reconcile
========================================================================
Reconcile rules with bank statement name.
This will reconcile multiple credit move lines (bank statements) with
all the lines from a specific bank statement, debit or credit (to also
reconcile the commission with credit card imports).
""",
'version': '1.0.0',
'author': 'Camptocamp',
'category': 'Finance',
'website': 'http://www.camptocamp.com',
'depends': ['account_advanced_reconcile'],
'data': ['easy_reconcile_view.xml'],
'demo': [],
'test': [],
'auto_install': False,
'installable': True,
'images': []
}

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Matthieu Dietrich. Copyright Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm
class easy_reconcile_advanced_bank_statement(orm.TransientModel):
_name = 'easy.reconcile.advanced.bank_statement'
_inherit = 'easy.reconcile.advanced'
def _base_columns(self, rec):
""" Mandatory columns for move lines queries
An extra column aliased as ``key`` should be defined
in each query."""
aml_cols = super(easy_reconcile_advanced_bank_statement, self).\
_base_columns(rec)
aml_cols += ['account_move_line.statement_id',
'account_bank_statement.name as statement_name',
]
return aml_cols
def _from(self, rec, *args, **kwargs):
result = super(easy_reconcile_advanced_bank_statement, self).\
_from(rec, *args, **kwargs)
result = result + (
" INNER JOIN account_bank_statement "
"ON account_bank_statement.id = account_move_line.statement_id "
)
return result
def _skip_line(self, cr, uid, rec, move_line, context=None):
"""
When True is returned on some conditions, the credit move line
will be skipped for reconciliation. Can be inherited to
skip on some conditions. ie: ref or partner_id is empty.
"""
result = super(easy_reconcile_advanced_bank_statement, self).\
_skip_line(cr, uid, rec, move_line, context=context)
if result:
return result
return not (move_line.get('ref') and
move_line.get('partner_id'))
def _matchers(self, cr, uid, rec, move_line, context=None):
return (('partner_id', move_line['partner_id']),
('ref', move_line['ref'].lower().strip()))
def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
yield ('partner_id', move_line['partner_id'])
yield ('ref',
(move_line['statement_name'] or '').lower().strip())
# Re-defined for this particular rule; since the commission line is a
# credit line inside of the target statement, it should also be considered
# as an opposite to be reconciled.
# And also, given some are refunds, debit lines can be "credit".
def _action_rec(self, cr, uid, rec, context=None):
credit_lines = self._query_credit(cr, uid, rec, context=context)
debit_lines = self._query_debit(cr, uid, rec, context=context)
return self._rec_auto_lines_advanced(cr, uid, rec,
credit_lines + debit_lines,
credit_lines + debit_lines,
context=context)

View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Matthieu Dietrich
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm
class account_easy_reconcile_method(orm.Model):
_inherit = 'account.easy.reconcile.method'
def _get_all_rec_method(self, cr, uid, context=None):
methods = super(account_easy_reconcile_method, self).\
_get_all_rec_method(cr, uid, context=context)
methods += [
('easy.reconcile.advanced.bank_statement',
'Advanced. Partner and Bank Statement'),
]
return methods

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="view_easy_reconcile_form" model="ir.ui.view">
<field name="name">account.easy.reconcile.form</field>
<field name="model">account.easy.reconcile</field>
<field name="inherit_id" ref="account_easy_reconcile.account_easy_reconcile_form"/>
<field name="arch" type="xml">
<page name="information" position="inside">
<group colspan="2" col="2">
<separator colspan="4" string="Advanced. Partner and Bank Statement"/>
<label string="Match multiple debit vs multiple credit entries. Allow partial reconciliation.
The lines should have the partner, the credit entry reference is matched vs the debit entry bank statement name." colspan="4"/>
</group>
</page>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,107 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_advanced_reconcile_bank_statement
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-05-27 08:36+0000\n"
"PO-Revision-Date: 2014-05-27 08:36+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_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,filter:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_filter
msgid "Filter"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,account_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_account_id
msgid "Account"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,partner_ids:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_partner_ids
msgid "Restrict on partners"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: code:_description:0
#: model:ir.model,name:account_advanced_reconcile_bank_statement.model_easy_reconcile_advanced_bank_statement
#, python-format
msgid "easy.reconcile.advanced.bank_statement"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: view:account.easy.reconcile:0
msgid "Match multiple debit vs multiple credit entries. Allow partial reconciliation. The lines should have the partner, the credit entry reference is matched vs the debit entry bank statement name."
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,journal_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_journal_id
msgid "Journal"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,account_profit_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_account_profit_id
msgid "Account Profit"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,analytic_account_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_analytic_account_id
msgid "Analytic Account"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: code:_description:0
#: model:ir.model,name:account_advanced_reconcile_bank_statement.model_account_easy_reconcile_method
#, python-format
msgid "reconcile method for account_easy_reconcile"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,date_base_on:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_date_base_on
msgid "Date of reconciliation"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: help:easy.reconcile.advanced.bank_statement,analytic_account_id:0
msgid "Analytic account for the write-off"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: view:account.easy.reconcile:0
msgid "Advanced. Partner and Bank Statement"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: code:_description:0
#: model:ir.model,name:account_advanced_reconcile_bank_statement.model_easy_reconcile_advanced
#, python-format
msgid "easy.reconcile.advanced"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,account_lost_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_account_lost_id
msgid "Account Lost"
msgstr ""
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,write_off:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_write_off
msgid "Write off allowed"
msgstr ""

View File

@@ -0,0 +1,103 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_advanced_reconcile_bank_statement
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-05-27 08:36+0000\n"
"PO-Revision-Date: 2014-05-27 08:36+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_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,filter:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_filter
msgid "Filter"
msgstr "Filtre"
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,account_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_account_id
msgid "Account"
msgstr "Compte"
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,partner_ids:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_partner_ids
msgid "Restrict on partners"
msgstr "Restriction sur les partenaires"
#. module: account_advanced_reconcile_bank_statement
#: code:_description:0
#: model:ir.model,name:account_advanced_reconcile_bank_statement.model_easy_reconcile_advanced_bank_statement
#, python-format
msgid "easy.reconcile.advanced.bank_statement"
msgstr "easy.reconcile.advanced.bank_statement"
#. module: account_advanced_reconcile_bank_statement
#: view:account.easy.reconcile:0
msgid ""
"Match multiple debit vs multiple credit entries. Allow partial "
"reconciliation. The lines should have the partner, the credit entry "
"reference is matched vs the debit entry bank statement name."
msgstr ""
"Le lettrage peut s'effectuer sur plusieurs écritures de débit et crédit. Le "
"lettrage partiel est autorisé. Les écritures doivent avoir le même "
"partenaire et la référence sur les écritures de crédit doit se retrouver
"dans le nom du relevé bancaire des écritures de débit."
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,journal_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_journal_id
msgid "Journal"
msgstr "Journal"
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,account_profit_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_account_profit_id
msgid "Account Profit"
msgstr "Compte de produit"
#. module: account_advanced_reconcile_bank_statement
#: code:_description:0
#: model:ir.model,name:account_advanced_reconcile_bank_statement.model_account_easy_reconcile_method
#, python-format
msgid "reconcile method for account_easy_reconcile"
msgstr "Méthode de lettrage pour le module account_easy_reconcile"
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,date_base_on:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_date_base_on
msgid "Date of reconciliation"
msgstr "Date de lettrage"
#. module: account_advanced_reconcile_bank_statement
#: view:account.easy.reconcile:0
msgid "Advanced. Partner and Bank Statement"
msgstr "Avancé. Partenaire et Relevé Bancaire"
#. module: account_advanced_reconcile_bank_statement
#: code:_description:0
#: model:ir.model,name:account_advanced_reconcile_bank_statement.model_easy_reconcile_advanced
#, python-format
msgid "easy.reconcile.advanced"
msgstr "easy.reconcile.advanced"
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,account_lost_id:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_account_lost_id
msgid "Account Lost"
msgstr "Compte de charge"
#. module: account_advanced_reconcile_bank_statement
#: field:easy.reconcile.advanced.bank_statement,write_off:0
#: model:ir.model.fields,field_description:account_advanced_reconcile_bank_statement.field_easy_reconcile_advanced_bank_statement_write_off
msgid "Write off allowed"
msgstr "Ecart autorisé"

View File

@@ -30,16 +30,6 @@ class EasyReconcileAdvanced(orm.AbstractModel):
""" Mandatory columns for move lines queries """ Mandatory columns for move lines queries
An extra column aliased as ``key`` should be defined An extra column aliased as ``key`` should be defined
in each query.""" in each query."""
aml_cols = ( aml_cols = super(EasyReconcileAdvanced, self)._base_columns(rec)
'id', aml_cols.append('account_move_line.transaction_ref')
'debit', return aml_cols
'credit',
'date',
'period_id',
'ref',
'name',
'partner_id',
'account_id',
'move_id',
'transaction_ref')
return ["account_move_line.%s" % col for col in aml_cols]

View File

@@ -19,7 +19,8 @@
# #
############################################################################## ##############################################################################
import easy_reconcile from . import easy_reconcile
import base_reconciliation from . import base_reconciliation
import simple_reconciliation from . import simple_reconciliation
import easy_reconcile_history from . import easy_reconcile_history
from . import res_config

View File

@@ -58,7 +58,9 @@ allows multiple lines and partial.
"data": ["easy_reconcile.xml", "data": ["easy_reconcile.xml",
"easy_reconcile_history_view.xml", "easy_reconcile_history_view.xml",
"security/ir_rule.xml", "security/ir_rule.xml",
"security/ir.model.access.csv"], "security/ir.model.access.csv",
"res_config_view.xml",
],
'license': 'AGPL-3', 'license': 'AGPL-3',
"auto_install": False, "auto_install": False,
'installable': False, 'installable': False,

View File

@@ -71,6 +71,7 @@ class EasyReconcileBase(orm.AbstractModel):
'name', 'name',
'partner_id', 'partner_id',
'account_id', 'account_id',
'reconcile_partial_id',
'move_id') 'move_id')
return ["account_move_line.%s" % col for col in aml_cols] return ["account_move_line.%s" % col for col in aml_cols]
@@ -78,10 +79,15 @@ class EasyReconcileBase(orm.AbstractModel):
return "SELECT %s" % ', '.join(self._base_columns(rec)) return "SELECT %s" % ', '.join(self._base_columns(rec))
def _from(self, rec, *args, **kwargs): def _from(self, rec, *args, **kwargs):
return "FROM account_move_line" return ("FROM account_move_line "
"LEFT OUTER JOIN account_move_reconcile ON "
"(account_move_line.reconcile_partial_id "
"= account_move_reconcile.id)"
)
def _where(self, rec, *args, **kwargs): def _where(self, rec, *args, **kwargs):
where = ("WHERE account_move_line.account_id = %s " where = ("WHERE account_move_line.account_id = %s "
"AND COALESCE(account_move_reconcile.type,'') <> 'manual' "
"AND account_move_line.reconcile_id IS NULL ") "AND account_move_line.reconcile_id IS NULL ")
# it would be great to use dict for params # it would be great to use dict for params
# but as we use _where_calc in _get_filter # but as we use _where_calc in _get_filter
@@ -177,7 +183,7 @@ class EasyReconcileBase(orm.AbstractModel):
cr, uid, rec, lines, rec.date_base_on, context=context) cr, uid, rec, lines, rec.date_base_on, context=context)
rec_ctx = dict(context, date_p=date) rec_ctx = dict(context, date_p=date)
if below_writeoff: if below_writeoff:
if sum_credit < sum_debit: if sum_credit > sum_debit:
writeoff_account_id = rec.account_profit_id.id writeoff_account_id = rec.account_profit_id.id
else: else:
writeoff_account_id = rec.account_lost_id.id writeoff_account_id = rec.account_lost_id.id
@@ -195,10 +201,42 @@ class EasyReconcileBase(orm.AbstractModel):
context=rec_ctx) context=rec_ctx)
return True, True return True, True
elif allow_partial: elif allow_partial:
# Check if the group of move lines was already partially
# reconciled and if all the lines were the same, in such
# case, just skip the group and consider it as partially
# reconciled (no change).
if lines:
existing_partial_id = lines[0]['reconcile_partial_id']
if existing_partial_id:
partial_line_ids = set(ml_obj.search(
cr, uid,
[('reconcile_partial_id', '=', existing_partial_id)],
context=context))
if set(line_ids) == partial_line_ids:
return True, False
# We need to give a writeoff_acc_id
# in case we have a multi currency lines
# to reconcile.
# If amount in currency is equal between
# lines to reconcile
# it will do a full reconcile instead of a partial reconcile
# and make a write-off for exchange
if sum_credit > sum_debit:
writeoff_account_id = rec.income_exchange_account_id.id
else:
writeoff_account_id = rec.expense_exchange_account_id.id
period_id = self.pool['account.period'].find(
cr, uid, dt=date, context=context)[0]
if rec.analytic_account_id:
rec_ctx['analytic_id'] = rec.analytic_account_id.id
ml_obj.reconcile_partial( ml_obj.reconcile_partial(
cr, uid, cr, uid,
line_ids, line_ids,
type='manual', type='manual',
writeoff_acc_id=writeoff_account_id,
writeoff_period_id=period_id,
writeoff_journal_id=rec.journal_id.id,
context=rec_ctx) context=rec_ctx)
return True, False return True, False
return False, False return False, False

View File

@@ -19,8 +19,14 @@
# #
############################################################################## ##############################################################################
from datetime import datetime
from openerp.osv import fields, orm from openerp.osv import fields, orm
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _ from openerp.tools.translate import _
from openerp import pooler
import logging
_logger = logging.getLogger(__name__)
class EasyReconcileOptions(orm.AbstractModel): class EasyReconcileOptions(orm.AbstractModel):
@@ -60,6 +66,11 @@ class EasyReconcileOptions(orm.AbstractModel):
'analytic_account_id': fields.many2one( 'analytic_account_id': fields.many2one(
'account.analytic.account', 'Analytic Account', 'account.analytic.account', 'Analytic Account',
help="Analytic account for the write-off"), help="Analytic account for the write-off"),
'income_exchange_account_id': fields.many2one(
'account.account', 'Gain Exchange Rate Account'),
'expense_exchange_account_id': fields.many2one(
'account.account', 'Loss Exchange Rate Account'),
} }
_defaults = { _defaults = {
@@ -131,6 +142,7 @@ class AccountEasyReconcileMethod(orm.Model):
class AccountEasyReconcile(orm.Model): class AccountEasyReconcile(orm.Model):
_name = 'account.easy.reconcile' _name = 'account.easy.reconcile'
_inherit = ['mail.thread']
_description = 'account easy reconcile' _description = 'account easy reconcile'
def _get_total_unrec(self, cr, uid, ids, name, arg, context=None): def _get_total_unrec(self, cr, uid, ids, name, arg, context=None):
@@ -159,11 +171,16 @@ class AccountEasyReconcile(orm.Model):
def _last_history(self, cr, uid, ids, name, args, context=None): def _last_history(self, cr, uid, ids, name, args, context=None):
result = {} result = {}
for history in self.browse(cr, uid, ids, context=context): # do a search() for retrieving the latest history line,
result[history.id] = False # as a read() will badly split the list of ids with 'date desc'
if history.history_ids: # and return the wrong result.
# history is sorted by date desc history_obj = self.pool['easy.reconcile.history']
result[history.id] = history.history_ids[0].id for reconcile_id in ids:
last_history = history_obj.search(
cr, uid, [('easy_reconcile_id', '=', reconcile_id)],
limit=1, order='date desc', context=context
)
result[reconcile_id] = last_history[0] if last_history else False
return result return result
_columns = { _columns = {
@@ -202,13 +219,19 @@ class AccountEasyReconcile(orm.Model):
rec_method.account_profit_id.id), rec_method.account_profit_id.id),
'analytic_account_id': (rec_method.analytic_account_id and 'analytic_account_id': (rec_method.analytic_account_id and
rec_method.analytic_account_id.id), rec_method.analytic_account_id.id),
'income_exchange_account_id':
(rec_method.income_exchange_account_id and
rec_method.income_exchange_account_id.id),
'expense_exchange_account_id':
(rec_method.income_exchange_account_id and
rec_method.income_exchange_account_id.id),
'journal_id': (rec_method.journal_id and 'journal_id': (rec_method.journal_id and
rec_method.journal_id.id), rec_method.journal_id.id),
'date_base_on': rec_method.date_base_on, 'date_base_on': rec_method.date_base_on,
'filter': rec_method.filter} 'filter': rec_method.filter}
def run_reconcile(self, cr, uid, ids, context=None): def run_reconcile(self, cr, uid, ids, context=None):
def find_reconcile_ids(fieldname, move_line_ids): def find_reconcile_ids(cr, fieldname, move_line_ids):
if not move_line_ids: if not move_line_ids:
return [] return []
sql = ("SELECT DISTINCT " + fieldname + sql = ("SELECT DISTINCT " + fieldname +
@@ -219,41 +242,75 @@ class AccountEasyReconcile(orm.Model):
res = cr.fetchall() res = cr.fetchall()
return [row[0] for row in res] return [row[0] for row in res]
# we use a new cursor to be able to commit the reconciliation
# often. We have to create it here and not later to avoid problems
# where the new cursor sees the lines as reconciles but the old one
# does not.
if context is None:
context = {}
for rec in self.browse(cr, uid, ids, context=context): for rec in self.browse(cr, uid, ids, context=context):
all_ml_rec_ids = [] ctx = context.copy()
all_ml_partial_ids = [] ctx['commit_every'] = (
rec.account.company_id.reconciliation_commit_every
)
if ctx['commit_every']:
new_cr = pooler.get_db(cr.dbname).cursor()
else:
new_cr = cr
try:
all_ml_rec_ids = []
all_ml_partial_ids = []
for method in rec.reconcile_method: for method in rec.reconcile_method:
rec_model = self.pool.get(method.name) rec_model = self.pool.get(method.name)
auto_rec_id = rec_model.create( auto_rec_id = rec_model.create(
cr, uid, new_cr, uid,
self._prepare_run_transient( self._prepare_run_transient(
cr, uid, method, context=context), new_cr, uid, method, context=context),
context=context) context=context)
ml_rec_ids, ml_partial_ids = rec_model.automatic_reconcile( ml_rec_ids, ml_partial_ids = rec_model.automatic_reconcile(
cr, uid, auto_rec_id, context=context) new_cr, uid, auto_rec_id, context=ctx)
all_ml_rec_ids += ml_rec_ids all_ml_rec_ids += ml_rec_ids
all_ml_partial_ids += ml_partial_ids all_ml_partial_ids += ml_partial_ids
reconcile_ids = find_reconcile_ids( reconcile_ids = find_reconcile_ids(
'reconcile_id', all_ml_rec_ids) new_cr, 'reconcile_id', all_ml_rec_ids)
partial_ids = find_reconcile_ids( partial_ids = find_reconcile_ids(
'reconcile_partial_id', all_ml_partial_ids) new_cr, 'reconcile_partial_id', all_ml_partial_ids)
self.pool.get('easy.reconcile.history').create( self.pool.get('easy.reconcile.history').create(new_cr, uid, {
cr, 'easy_reconcile_id': rec.id,
uid, 'date': fields.datetime.now(),
{'easy_reconcile_id': rec.id, 'reconcile_ids': [(4, rid) for rid in reconcile_ids],
'date': fields.datetime.now(), 'reconcile_partial_ids': [(4, rid) for rid in partial_ids],
'reconcile_ids': [(4, rid) for rid in reconcile_ids], }, context=context)
'reconcile_partial_ids': [(4, rid) for rid in partial_ids]}, except Exception as e:
context=context) # In case of error, we log it in the mail thread, log the
# stack trace and create an empty history line; otherwise,
# the cron will just loop on this reconcile task.
_logger.exception("The reconcile task %s had an exception: %s",
rec.name, e.value)
message = "There was an error during reconciliation : %s" \
% e.value
self.message_post(cr, uid, rec.id,
body=message, context=context)
self.pool.get('easy.reconcile.history').create(new_cr, uid, {
'easy_reconcile_id': rec.id,
'date': fields.datetime.now(),
'reconcile_ids': [],
'reconcile_partial_ids': [],
})
finally:
if ctx['commit_every']:
new_cr.commit()
new_cr.close()
return True return True
def _no_history(self, cr, uid, rec, context=None): def _no_history(self, cr, uid, rec, context=None):
""" Raise an `osv.except_osv` error, supposed to """ Raise an `orm.except_orm` error, supposed to
be called when there is no history on the reconciliation be called when there is no history on the reconciliation
task. task.
""" """
@@ -333,3 +390,30 @@ class AccountEasyReconcile(orm.Model):
if not rec.last_history: if not rec.last_history:
self._no_history(cr, uid, rec, context=context) self._no_history(cr, uid, rec, context=context)
return rec.last_history.open_partial() return rec.last_history.open_partial()
def run_scheduler(self, cr, uid, run_all=None, context=None):
""" Launch the reconcile with the oldest run
This function is mostly here to be used with cron task
:param run_all: if set it will ingore lookup and launch
all reconciliation
:returns: True in case of success or raises an exception
"""
def _get_date(reconcile):
if reconcile.last_history.date:
return datetime.strptime(reconcile.last_history.date,
DEFAULT_SERVER_DATETIME_FORMAT)
else:
return datetime.min
ids = self.search(cr, uid, [], context=context)
assert ids, "No easy reconcile available"
if run_all:
self.run_reconcile(cr, uid, ids, context=context)
return True
reconciles = self.browse(cr, uid, ids, context=context)
reconciles.sort(key=_get_date)
older = reconciles[0]
self.run_reconcile(cr, uid, [older.id], context=context)
return True

View File

@@ -71,6 +71,10 @@ The lines should have the same amount (with the write-off) and the same referenc
</page> </page>
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form> </form>
</field> </field>
</record> </record>
@@ -129,6 +133,8 @@ The lines should have the same amount (with the write-off) and the same referenc
<field name="write_off"/> <field name="write_off"/>
<field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/> <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/> <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="income_exchange_account_id" groups="base.group_multi_currency"/>
<field name="expense_exchange_account_id" groups="base.group_multi_currency"/>
<field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/> <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/> <field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="date_base_on"/> <field name="date_base_on"/>
@@ -147,6 +153,8 @@ The lines should have the same amount (with the write-off) and the same referenc
<field name="write_off"/> <field name="write_off"/>
<field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/> <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/> <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="income_exchange_account_id" groups="base.group_multi_currency"/>
<field name="expense_exchange_account_id" groups="base.group_multi_currency"/>
<field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/> <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/> <field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="date_base_on"/> <field name="date_base_on"/>
@@ -161,4 +169,22 @@ The lines should have the same amount (with the write-off) and the same referenc
parent="account.periodical_processing_reconciliation"/> parent="account.periodical_processing_reconciliation"/>
</data> </data>
<data noupdate="1">
<record forcecreate="True" id="ir_cron_run_reconciliations" model="ir.cron">
<field name="name">Do Automatic Reconciliations</field>
<field eval="False" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">3</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
<field eval="'account.easy.reconcile'" name="model"/>
<field eval="'run_scheduler'" name="function"/>
<field eval="'()'" name="args"/>
</record>
</data>
</openerp> </openerp>

View File

@@ -53,7 +53,7 @@ class EasyReconcileHistory(orm.Model):
_columns = { _columns = {
'easy_reconcile_id': fields.many2one( 'easy_reconcile_id': fields.many2one(
'account.easy.reconcile', 'Reconcile Profile', readonly=True), 'account.easy.reconcile', 'Reconcile Profile', readonly=True),
'date': fields.datetime('Run date', readonly=True), 'date': fields.datetime('Run date', readonly=True, required=True),
'reconcile_ids': fields.many2many( 'reconcile_ids': fields.many2many(
'account.move.reconcile', 'account.move.reconcile',
'account_move_reconcile_history_rel', 'account_move_reconcile_history_rel',
@@ -62,28 +62,27 @@ class EasyReconcileHistory(orm.Model):
'account.move.reconcile', 'account.move.reconcile',
'account_move_reconcile_history_partial_rel', 'account_move_reconcile_history_partial_rel',
string='Partial Reconciliations', readonly=True), string='Partial Reconciliations', readonly=True),
'reconcile_line_ids': 'reconcile_line_ids': fields.function(
fields.function(
_reconcile_line_ids, _reconcile_line_ids,
string='Reconciled Items', string='Reconciled Items',
type='many2many', type='many2many',
relation='account.move.line', relation='account.move.line',
readonly=True, readonly=True,
multi='lines'), multi='lines'),
'partial_line_ids': 'partial_line_ids': fields.function(
fields.function( _reconcile_line_ids,
_reconcile_line_ids, string='Partially Reconciled Items',
string='Partially Reconciled Items', type='many2many',
type='many2many', relation='account.move.line',
relation='account.move.line', readonly=True,
readonly=True, multi='lines'),
multi='lines'), 'company_id': fields.related(
'company_id': fields.related('easy_reconcile_id', 'company_id', 'easy_reconcile_id', 'company_id',
relation='res.company', relation='res.company',
type='many2one', type='many2one',
string='Company', string='Company',
store=True, store=True,
readonly=True), readonly=True),
} }

View File

@@ -404,3 +404,24 @@ msgstr ""
msgid "account easy reconcile" msgid "account easy reconcile"
msgstr "" msgstr ""
#. module: account_easy_reconcile
#: field:account.easy.reconcile.method,expense_exchange_account_id:0
#: field:easy.reconcile.base,expense_exchange_account_id:0
#: field:easy.reconcile.options,expense_exchange_account_id:0
#: field:easy.reconcile.simple,expense_exchange_account_id:0
#: field:easy.reconcile.simple.name,expense_exchange_account_id:0
#: field:easy.reconcile.simple.partner,expense_exchange_account_id:0
#: field:easy.reconcile.simple.reference,expense_exchange_account_id:0
msgid "Loss Exchange Rate Account"
msgstr ""
#. module: account_easy_reconcile
#: field:account.easy.reconcile.method,income_exchange_account_id:0
#: field:easy.reconcile.base,income_exchange_account_id:0
#: field:easy.reconcile.options,income_exchange_account_id:0
#: field:easy.reconcile.simple,income_exchange_account_id:0
#: field:easy.reconcile.simple.name,income_exchange_account_id:0
#: field:easy.reconcile.simple.partner,income_exchange_account_id:0
#: field:easy.reconcile.simple.reference,income_exchange_account_id:0
msgid "Gain Exchange Rate Account"
msgstr ""

View File

@@ -427,3 +427,25 @@ msgstr "easy.reconcile.simple.reference"
#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile #: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile
msgid "account easy reconcile" msgid "account easy reconcile"
msgstr "Lettrage automatisé" msgstr "Lettrage automatisé"
#. module: account_easy_reconcile
#: field:account.easy.reconcile.method,expense_exchange_account_id:0
#: field:easy.reconcile.base,expense_exchange_account_id:0
#: field:easy.reconcile.options,expense_exchange_account_id:0
#: field:easy.reconcile.simple,expense_exchange_account_id:0
#: field:easy.reconcile.simple.name,expense_exchange_account_id:0
#: field:easy.reconcile.simple.partner,expense_exchange_account_id:0
#: field:easy.reconcile.simple.reference,expense_exchange_account_id:0
msgid "Loss Exchange Rate Account"
msgstr "Compte de perte de change"
#. module: account_easy_reconcile
#: field:account.easy.reconcile.method,income_exchange_account_id:0
#: field:easy.reconcile.base,income_exchange_account_id:0
#: field:easy.reconcile.options,income_exchange_account_id:0
#: field:easy.reconcile.simple,income_exchange_account_id:0
#: field:easy.reconcile.simple.name,income_exchange_account_id:0
#: field:easy.reconcile.simple.partner,income_exchange_account_id:0
#: field:easy.reconcile.simple.reference,income_exchange_account_id:0
msgid "Gain Exchange Rate Account"
msgstr "Compte de gain de change"

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Leonardo Pistone
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class AccountConfigSettings(orm.TransientModel):
_inherit = 'account.config.settings'
_columns = {
'reconciliation_commit_every': fields.related(
'company_id',
'reconciliation_commit_every',
type='integer',
string='How often to commit when performing automatic '
'reconciliation.',
help="""Leave zero to commit only at the end of the process."""),
}
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
company_obj = self.pool['res.company']
result = super(AccountConfigSettings, self).onchange_company_id(
cr, uid, ids, company_id, context=None)
if company_id:
company = company_obj.browse(cr, uid, company_id, context=context)
result['value']['reconciliation_commit_every'] = (
company.reconciliation_commit_every
)
return result
class Company(orm.Model):
_inherit = "res.company"
_columns = {
'reconciliation_commit_every': fields.integer(
string='How often to commit when performing automatic '
'reconciliation.',
help="""Leave zero to commit only at the end of the process."""),
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_account_config" model="ir.ui.view">
<field name="name">account settings</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="arch" type="xml">
<separator string="eInvoicing &amp; Payments" position="before">
<separator string="Reconciliation"/>
<group>
<label for="id" string="Options"/>
<div name="reconciliation_config">
<div>
<label for="reconciliation_commit_every"/>
<field name="reconciliation_commit_every" class="oe_inline"/>
</div>
</div>
</group>
</separator>
</field>
</record>
</data>
</openerp>

View File

@@ -18,4 +18,5 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# #
import statement from . import statement
from . import res_partner_bank

View File

@@ -0,0 +1,52 @@
#
# Authors: Laurent Mignon
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
from openerp.osv.orm import Model
class res_partner_bank(Model):
_inherit = 'res.partner.bank'
def search_by_acc_number(self, cr, uid, acc_number, context=None):
'''
Try to find the Account Number using a 'like' operator to avoid
problems with the input mask used to store the value.
'''
# first try with an exact match
ids = self.search(cr,
uid,
[('acc_number', '=', acc_number)],
context=context)
if ids:
return ids
cr.execute("""
SELECT
id
FROM
res_partner_bank
WHERE
regexp_replace(acc_number,'([^[:alnum:]])', '','g')
ilike
regexp_replace(%s,'([^[:alnum:]])', '','g')
""", (acc_number,))
# apply security constraints by using the orm
return self.search(cr, uid,
[('id', 'in', [r[0] for r in cr.fetchall()])],
context=context)

View File

@@ -23,8 +23,8 @@
from openerp.tools.translate import _ from openerp.tools.translate import _
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields from openerp.osv import fields
from openerp.addons.account_statement_base_completion.statement import \ from openerp.addons.account_statement_base_completion.statement \
ErrorTooManyPartner import ErrorTooManyPartner
class AccountStatementCompletionRule(Model): class AccountStatementCompletionRule(Model):
@@ -54,21 +54,25 @@ class AccountStatementCompletionRule(Model):
partner_acc_number = st_line['partner_acc_number'] partner_acc_number = st_line['partner_acc_number']
if not partner_acc_number: if not partner_acc_number:
return {} return {}
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool['account.bank.statement.line']
res = {} res = {}
res_bank_obj = self.pool.get('res.partner.bank') res_bank_obj = self.pool['res.partner.bank']
ids = res_bank_obj.search(cr, ids = res_bank_obj.search_by_acc_number(cr,
uid, uid,
[('acc_number', '=', partner_acc_number)], partner_acc_number,
context=context) context=context)
if len(ids) > 1: if len(ids) > 1:
raise ErrorTooManyPartner( raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched '
_('Line named "%s" (Ref:%s) was matched by more than one ' 'by more than one partner for account '
'partner for account number "%s".') % 'number "%s".') %
(st_line['name'], st_line['ref'], partner_acc_number)) (st_line['name'],
st_line['ref'],
partner_acc_number))
if len(ids) == 1: if len(ids) == 1:
partner = res_bank_obj.browse( partner = res_bank_obj.browse(cr,
cr, uid, ids[0], context=context).partner_id uid,
ids[0],
context=context).partner_id
res['partner_id'] = partner.id res['partner_id'] = partner.id
st_vals = st_obj.get_values_for_line( st_vals = st_obj.get_values_for_line(
cr, uid, profile_id=st_line['profile_id'], cr, uid, profile_id=st_line['profile_id'],

View File

@@ -22,81 +22,102 @@
from openerp.tests import common from openerp.tests import common
import time import time
ACC_NUMBER = "BE38733040385372" ACC_NUMBER = " BE38 7330 4038 5372 "
class bankaccount_completion(common.TransactionCase): class bankaccount_completion(common.TransactionCase):
def prepare(self): def setUp(self):
super(bankaccount_completion, self).setUp()
self.company_a = self.browse_ref('base.main_company') self.company_a = self.browse_ref('base.main_company')
self.profile_obj = self.registry("account.statement.profile") self.profile_obj = self.registry("account.statement.profile")
self.st_obj = self.registry("account.bank.statement") self.acc_bk_stmt = self.registry("account.bank.statement")
self.st_line_obj = self.registry("account.bank.statement.line") self.st_line_obj = self.registry("account.bank.statement.line")
self.completion_rule_id = self.ref( self.completion_rule_id = \
'account_statement_bankaccount_completion.' self.ref('account_statement_bankaccount_completion.'
'bank_statement_completion_rule_10') 'bank_statement_completion_rule_10')
self.journal_id = self.registry("ir.model.data").get_object_reference( self.journal_id = self.ref("account.bank_journal")
self.cr, self. uid, "account", "bank_journal")[1]
self.partner_id = self.ref('base.main_partner') self.partner_id = self.ref('base.main_partner')
self.account_id = self.ref("account.a_recv")
# Create the profile # Create the profile
self.account_id = self.registry("ir.model.data").get_object_reference(
self.cr, self.uid, "account", "a_recv")[1]
self.journal_id = self.registry("ir.model.data").get_object_reference(
self.cr, self. uid, "account", "bank_journal")[1]
self.profile_id = self.profile_obj.create(self.cr, self.uid, { self.profile_id = self.profile_obj.create(self.cr, self.uid, {
"name": "TEST", "name": "TEST",
"commission_account_id": self.account_id, "commission_account_id": self.account_id,
"journal_id": self.journal_id, "journal_id": self.journal_id,
"rule_ids": [(6, 0, [self.completion_rule_id])]}) "rule_ids": [(6, 0, [self.completion_rule_id])]})
# Create the completion rule
# Create a bank statement # Create a bank statement
self.statement_id = self.st_obj.create( vals = {"balance_end_real": 0.0,
self.cr, self.uid, {
"balance_end_real": 0.0,
"balance_start": 0.0, "balance_start": 0.0,
"date": time.strftime('%Y-%m-%d'), "date": time.strftime('%Y-%m-%d'),
"journal_id": self.journal_id, "journal_id": self.journal_id,
"profile_id": self.profile_id "profile_id": self.profile_id,
}) }
self.statement_id = self.acc_bk_stmt.create(self.cr,
# Create bank a statement line self.uid,
self.statement_line_id = self.st_line_obj.create(self.cr, self.uid, { vals)
'amount': 1000.0,
'name': 'EXT001',
'ref': 'My ref',
'statement_id': self.statement_id,
'partner_acc_number': ACC_NUMBER
})
# Add a bank account number to the partner # Add a bank account number to the partner
res_bank_obj = self.registry('res.partner.bank') self.res_partner_bank_obj = self.registry('res.partner.bank')
res_bank_obj.create(self.cr, self.uid, { vals = {"state": "bank",
"state": "bank", "company_id": self.company_a.id,
"company_id": self.company_a.id, "partner_id": self.partner_id,
"partner_id": self.partner_id, "acc_number": ACC_NUMBER,
"acc_number": ACC_NUMBER, "footer": True,
"footer": True, "bank_name": "Reserve",
"bank_name": "Reserve" }
}) self.res_partner_bank_id = self.res_partner_bank_obj.create(self.cr,
self.uid,
vals)
def test_00(self): def test_00(self):
"""Test complete partner_id from bank account number """Test complete partner_id from bank account number
Test the automatic completion of the partner_id based on the account Test the automatic completion of the partner_id based on the account
number associated to the number associated to the statement line
statement line
""" """
self.prepare() for bank_acc_number in [ACC_NUMBER, ACC_NUMBER.replace(" ", ""),
statement_line = self.st_line_obj.browse(self.cr, self.uid, ACC_NUMBER.replace(" ", "-")]:
self.statement_line_id) # check the completion for well formatted and not well
# before import, the # formatted account number
self.assertFalse( self.res_partner_bank_obj.write(self.cr,
statement_line.partner_id, self.uid,
"Partner_id must be blank before completion") self.res_partner_bank_id,
statement_obj = self.st_obj.browse(self.cr, self.uid, self.statement_id) {"acc_number": bank_acc_number}
statement_obj.button_auto_completion() )
statement_line = self.st_line_obj.browse(self.cr, self.uid, for acc_number in [ACC_NUMBER, ACC_NUMBER.replace(" ", ""),
self.statement_line_id) ACC_NUMBER.replace(" ", "-"),
self.assertEquals(self.partner_id, statement_line.partner_id[ " BE38-7330 4038-5372 "]:
'id'], "Missing expected partner id after completion") vals = {'amount': 1000.0,
'name': 'EXT001',
'ref': 'My ref',
'statement_id': self.statement_id,
'partner_acc_number': acc_number
}
line_id = self.st_line_obj.create(self.cr, self.uid, vals)
line = self.st_line_obj.browse(self.cr, self.uid, line_id)
self.assertFalse(line.partner_id,
'Partner_id must be blank before completion')
statement_obj = self.acc_bk_stmt.browse(self.cr,
self.uid,
self.statement_id)
statement_obj.button_auto_completion()
line = self.st_line_obj.browse(self.cr, self.uid, line_id)
self.assertEquals(self.partner_id, line.partner_id['id'],
'Missing expected partner id after '
'completion')
vals = {'amount': 1000.0,
'name': 'EXT001',
'ref': 'My ref',
'statement_id': self.statement_id,
'partner_acc_number': 'BE38a7330.4038-5372.',
}
line_id = self.st_line_obj.create(self.cr, self.uid, vals)
line = self.st_line_obj.browse(self.cr, self.uid, line_id)
self.assertFalse(line.partner_id,
'Partner_id must be blank before completion')
statement_obj = self.acc_bk_stmt.browse(self.cr,
self.uid,
self.statement_id)
statement_obj.button_auto_completion()
line = self.st_line_obj.browse(self.cr, self.uid, line_id)
self.assertFalse(line.partner_id.id)

View File

@@ -26,13 +26,14 @@
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', 'complexity': 'normal',
'depends': ['account_statement_ext'], 'depends': ['account_statement_ext',
'account_report_company'],
'description': """ 'description': """
The goal of this module is to improve the basic bank statement, help dealing The goal of this module is to improve the basic bank statement, help dealing
with huge volume of reconciliation by providing basic rules to identify the with huge volume of reconciliation by providing basic rules to identify the
partner of a bank statement line. partner of a bank statement line.
Each bank statement profile can have its own rules to be applied according to a Each bank statement profile can have its own rules to be applied according to
sequence order. a sequence order.
Some basic rules are provided in this module: Some basic rules are provided in this module:
@@ -41,15 +42,15 @@
2) Match from statement line label (based on partner name) 2) Match from statement line label (based on partner name)
3) Match from statement line reference (based on Invoice number) 3) Match from statement line reference (based on Invoice number)
You can easily override this module and add your own rules in your own one. The You can easily override this module and add your own rules in your own one.
basic rules only fill in the partner, but you can use them to fill in any The basic rules only fill in the partner, but you can use them to fill in
value of the line (in the future, we will add a rule to automatically match and any value of the line (in the future, we will add a rule to automatically
reconcile the line). match and reconcile the line).
It adds as well a label on the bank statement line (on which the pre-define It adds as well a label on the bank statement line (on which the pre-define
rules can match) and a char field on the partner called 'Bank Statement Label'. rules can match) and a char field on the partner called 'Bank Statement
Using the pre-define rules, you will be able to match various labels for a Label'. Using the pre-define rules, you will be able to match various
partner. labels for a partner.
The reference of the line is always used by the reconciliation process. We're The reference of the line is always used by the reconciliation process. We're
supposed to copy there (or write manually) the matching string. This can be: supposed to copy there (or write manually) the matching string. This can be:
@@ -59,9 +60,10 @@
You can use it with our account_advanced_reconcile module to automatize the You can use it with our account_advanced_reconcile module to automatize the
reconciliation process. reconciliation process.
TODO: The rules that look for invoices to find out the partner should take back
the payable / receivable account from there directly instead of retrieving it TODO: The rules that look for invoices to find out the partner should take
from partner properties ! back the payable / receivable account from there directly instead of
retrieving it from partner properties!
""", """,
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',
'data': [ 'data': [
@@ -75,6 +77,7 @@
'test/partner.yml', 'test/partner.yml',
'test/invoice.yml', 'test/invoice.yml',
'test/supplier_invoice.yml', 'test/supplier_invoice.yml',
'test/refund.yml',
'test/completion_test.yml' 'test/completion_test.yml'
], ],
'installable': False, 'installable': False,

View File

@@ -158,7 +158,7 @@ msgstr "Erreur système"
#. module: account_statement_base_completion #. module: account_statement_base_completion
#: field:account.bank.statement.line,already_completed:0 #: field:account.bank.statement.line,already_completed:0
msgid "Auto-Completed" msgid "Auto-Completed"
msgstr "Auto-Completé" msgstr "Auto-Complété"
#. module: account_statement_base_completion #. module: account_statement_base_completion
#: code:addons/account_statement_base_completion/statement.py:448 #: code:addons/account_statement_base_completion/statement.py:448
@@ -170,7 +170,7 @@ msgstr "Erreur de bypass de l'ORM"
#. module: account_statement_base_completion #. module: account_statement_base_completion
#: field:account.statement.completion.rule,sequence:0 #: field:account.statement.completion.rule,sequence:0
msgid "Sequence" msgid "Sequence"
msgstr "Séquence" msgstr "Séquence"
#. module: account_statement_base_completion #. module: account_statement_base_completion
#: code:addons/account_statement_base_completion/statement.py:280 #: code:addons/account_statement_base_completion/statement.py:280
@@ -210,7 +210,7 @@ msgstr ""
"Entrez les différentes descriptions/informations sur votre relevé bancaire " "Entrez les différentes descriptions/informations sur votre relevé bancaire "
"séparées par un ';' Si l'une d'entre elles figure dans la ligne du " "séparées par un ';' Si l'une d'entre elles figure dans la ligne du "
"relevé, le partenaire correspondant sera automatiquement retrouvé " "relevé, le partenaire correspondant sera automatiquement retrouvé "
"(A condition d'utiliser un règle de lettrage dans le profil)." "(à condition d'utiliser un règle de lettrage dans le profil)."
#. module: account_statement_base_completion #. module: account_statement_base_completion
#: model:ir.model,name:account_statement_base_completion.model_res_partner #: model:ir.model,name:account_statement_base_completion.model_res_partner

View File

@@ -32,8 +32,8 @@ class ResPartner(orm.Model):
'bank_statement_label': fields.char( 'bank_statement_label': fields.char(
'Bank Statement Label', size=100, 'Bank Statement Label', size=100,
help="Enter the various label found on your bank statement " help="Enter the various label found on your bank statement "
"separated by a ; If one of this label is include in the bank " "separated by a ; If one of this label is include in the "
"statement line, the partner will be automatically filled (as " "bank statement line, the partner will be automatically "
"long as you use this method/rules in your statement " "filled (as long as you use this method/rules in your "
"profile)."), "statement profile)."),
} }

View File

@@ -124,7 +124,9 @@ class AccountStatementCompletionRule(orm.Model):
_order = "sequence asc" _order = "sequence asc"
def _get_functions(self, cr, uid, context=None): def _get_functions(self, cr, uid, context=None):
"""List of available methods for rules. Override this to add you own.""" """List of available methods for rules.
Override this to add you own."""
return [ return [
('get_from_ref_and_invoice', ('get_from_ref_and_invoice',
'From line reference (based on customer invoice number)'), 'From line reference (based on customer invoice number)'),
@@ -189,13 +191,7 @@ class AccountStatementCompletionRule(orm.Model):
res = {} res = {}
inv = self._find_invoice(cr, uid, line, inv_type, context=context) inv = self._find_invoice(cr, uid, line, inv_type, context=context)
if inv: if inv:
# FIXME use only commercial_partner_id of invoice in 7.1 partner_id = inv.commercial_partner_id.id
# this is for backward compatibility in 7.0 before
# the refactoring of res.partner
if hasattr(inv, 'commercial_partner_id'):
partner_id = inv.commercial_partner_id.id
else:
partner_id = inv.partner_id.id
res = {'partner_id': partner_id, res = {'partner_id': partner_id,
'account_id': inv.account_id.id, 'account_id': inv.account_id.id,
'type': inv_type} 'type': inv_type}
@@ -225,8 +221,8 @@ class AccountStatementCompletionRule(orm.Model):
# Should be private but data are initialised with no update XML # Should be private but data are initialised with no update XML
def get_from_ref_and_invoice(self, cr, uid, line, context=None): def get_from_ref_and_invoice(self, cr, uid, line, context=None):
"""Match the partner based on the invoice number and the reference of """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 the statement line. Then, call the generic get_values_for_line method
complete other values. If more than one partner matched, raise the to complete other values. If more than one partner matched, raise the
ErrorTooManyPartner error. ErrorTooManyPartner error.
:param dict line: read of the concerned account.bank.statement.line :param dict line: read of the concerned account.bank.statement.line
@@ -242,11 +238,11 @@ class AccountStatementCompletionRule(orm.Model):
# Should be private but data are initialised with no update XML # Should be private but data are initialised with no update XML
def get_from_label_and_partner_field(self, cr, uid, st_line, context=None): def get_from_label_and_partner_field(self, cr, uid, st_line, context=None):
""" """
Match the partner based on the label field of the statement line Match the partner based on the label field of the statement line and
and the text defined in the 'bank_statement_label' field of the partner. the text defined in the 'bank_statement_label' field of the partner.
Remember that we can have values separated with ; Then, call the generic Remember that we can have values separated with ; Then, call the
get_values_for_line method to complete other values. generic get_values_for_line method to complete other values. If more
If more than one partner matched, raise the ErrorTooManyPartner error. than one partner matched, raise the ErrorTooManyPartner error.
:param dict st_line: read of the concerned account.bank.statement.line :param dict st_line: read of the concerned account.bank.statement.line
:return: :return:
@@ -303,10 +299,10 @@ class AccountStatementCompletionRule(orm.Model):
return res return res
def get_from_label_and_partner_name(self, cr, uid, st_line, context=None): def get_from_label_and_partner_name(self, cr, uid, st_line, context=None):
"""Match the partner based on the label field of the statement line """Match the partner based on the label field of the statement line and
and the name of the partner. Then, call the generic get_values_for_line 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 method to complete other values. If more than one partner matched,
the ErrorTooManyPartner error. raise the ErrorTooManyPartner error.
:param dict st_line: read of the concerned account.bank.statement.line :param dict st_line: read of the concerned account.bank.statement.line
:return: :return:
@@ -333,7 +329,7 @@ class AccountStatementCompletionRule(orm.Model):
# to: # to:
# http://www.postgresql.org/docs/9.0/static/functions-matching.html # http://www.postgresql.org/docs/9.0/static/functions-matching.html
# in chapter 9.7.3.6. Limits and Compatibility # in chapter 9.7.3.6. Limits and Compatibility
sql = """ sql = r"""
SELECT id FROM ( SELECT id FROM (
SELECT id, SELECT id,
regexp_matches(%s, regexp_matches(%s,
@@ -386,9 +382,9 @@ class AccountStatement(orm.Model):
class AccountStatementLine(orm.Model): class AccountStatementLine(orm.Model):
""" """
Add sparse field on the statement line to allow to store all the Add sparse field on the statement line to allow to store all the bank infos
bank infos that are given by a bank/office. You can then add you own in your that are given by a bank/office. You can then add you own in your module.
module. The idea here is to store all bank/office infos in the The idea here is to store all bank/office infos in the
additionnal_bank_fields serialized field when importing the file. If many 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. values, add a tab in the bank statement line to store your specific one.
Have a look in account_statement_base_import module to see how we've done Have a look in account_statement_base_import module to see how we've done
@@ -449,7 +445,8 @@ class AccountStatementLine(orm.Model):
statement_line_obj = self.pool['account.bank.statement.line'] statement_line_obj = self.pool['account.bank.statement.line']
model_cols = statement_line_obj._columns model_cols = statement_line_obj._columns
avail = [ avail = [
k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')] k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')
]
keys = [k for k in statement_store[0].keys() if k in avail] keys = [k for k in statement_store[0].keys() if k in avail]
# add sparse fields.. # add sparse fields..
if include_serializable: if include_serializable:
@@ -635,7 +632,8 @@ class AccountBankStatement(orm.Model):
st += ''.join(traceback.format_tb(trbk, 30)) st += ''.join(traceback.format_tb(trbk, 30))
_logger.error(st) _logger.error(st)
if res: if res:
# stat_line_obj.write(cr, uid, [line.id], vals, context=ctx) # stat_line_obj.write(cr, uid, [line.id], vals,
# context=ctx)
try: try:
stat_line_obj._update_line( stat_line_obj._update_line(
cr, uid, res, context=context) cr, uid, res, context=context)

View File

@@ -38,6 +38,15 @@
ref: T2S12345 ref: T2S12345
date: '2013-12-19' date: '2013-12-19'
amount: -65.0 amount: -65.0
-
I create a statement line for a CR
-
!record {model: account.bank.statement.line, id: statement_line_cr}:
name: Test autocompletion based on Customer Refund Number
statement_id: statement_test1
ref: CR0001
date: '2013-12-19'
amount: -210.0
- -
I create a statement line for the Partner Name I create a statement line for the Partner Name
- -
@@ -76,12 +85,18 @@
!assert {model: account.bank.statement.line, id: statement_line_si, string: Check completion by SI number}: !assert {model: account.bank.statement.line, id: statement_line_si, string: Check completion by SI number}:
- partner_id.id == _ref("base.res_partner_17") - partner_id.id == _ref("base.res_partner_17")
- -
Line 3. I check that the partner name has been recognised. Line 3. I expect the Customer refund number to be recognised. It should be
the commercial partner, and not the regular partner.
-
!assert {model: account.bank.statement.line, id: statement_line_cr, string: Check completion by CR number and commercial partner}:
- partner_id.id == _ref("base.res_partner_12")
-
Line 4. I check that the partner name has been recognised.
- -
!assert {model: account.bank.statement.line, id: statement_line_partner_name, string: Check completion by partner name}: !assert {model: account.bank.statement.line, id: statement_line_partner_name, string: Check completion by partner name}:
- partner_id.name == 'Vauxoo' - partner_id.name == 'Vauxoo'
- -
Line 4. I check that the partner special label has been recognised. Line 5. I check that the partner special label has been recognised.
- -
!assert {model: account.bank.statement.line, id: statement_line_partner_label, string: Check completion by partner label}: !assert {model: account.bank.statement.line, id: statement_line_partner_label, string: Check completion by partner label}:
- partner_id.id == _ref("base.res_partner_6") - partner_id.id == _ref("base.res_partner_6")

View File

@@ -0,0 +1,43 @@
-
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}:
account_id: account.a_pay
company_id: base.main_company
currency_id: base.EUR
internal_number: CR0001
invoice_line:
- account_id: account.a_expense
name: '[PCSC234] PC Assemble SC234'
price_unit: 210.0
quantity: 1.0
product_id: product.product_product_3
uos_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 "CR0001"
-
!assert {model: account.invoice, id: refund_for_completion_1, string: Check CI number}:
- number == 'CR0001'

View File

@@ -65,8 +65,8 @@ class base_completion(common.TransactionCase):
def test_name_completion(self): def test_name_completion(self):
"""Test complete partner_id from statement line label """Test complete partner_id from statement line label
Test the automatic completion of the partner_id based if the name of the Test the automatic completion of the partner_id based if the name of
partner appears in the statement line label the partner appears in the statement line label
""" """
self.completion_rule_id = self.ref( self.completion_rule_id = self.ref(
'account_statement_base_completion.' 'account_statement_base_completion.'
@@ -89,7 +89,8 @@ class base_completion(common.TransactionCase):
for case in NAMES_COMPLETION_CASES: for case in NAMES_COMPLETION_CASES:
self.partner_obj.write( self.partner_obj.write(
self.cr, self.uid, self.partner_id, {'name': case.partner_name}) self.cr, self.uid, self.partner_id, {'name': case.partner_name}
)
statement_line_id = self.account_bank_statement_line_obj.create( statement_line_id = self.account_bank_statement_line_obj.create(
self.cr, self.uid, { self.cr, self.uid, {
'amount': 1000.0, 'amount': 1000.0,
@@ -116,5 +117,6 @@ class base_completion(common.TransactionCase):
else: else:
self.assertNotEquals( self.assertNotEquals(
self.partner_id, statement_line.partner_id['id'], self.partner_id, statement_line.partner_id['id'],
"Partner id should be empty after completion(partner_name: " "Partner id should be empty after completion "
"%s, line_name: %s)" % (case.partner_name, case.line_label)) "(partner_name: %s, line_name: %s)"
% (case.partner_name, case.line_label))

View File

@@ -18,6 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
import parser from . import parser
import wizard from . import wizard
import statement from . import statement

View File

@@ -57,8 +57,8 @@
The goal is here to populate the statement lines of a bank statement with the The goal is here to populate the statement lines of a bank statement with the
infos that the bank or office give you. Fell free to inherit from this module infos that the bank or office give you. Fell free to inherit from this module
to add your own format. Then, if you need to complete data from there, add your to add your own format. Then, if you need to complete data from there, add
own account_statement_*_completion module and implement the needed rules. your own account_statement_*_completion module and implement the needed rules.
""", """,
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',
'data': [ 'data': [

View File

@@ -19,7 +19,7 @@
# #
############################################################################## ##############################################################################
from parser import new_bank_statement_parser from .parser import new_bank_statement_parser
from parser import BankStatementImportParser from .parser import BankStatementImportParser
import file_parser from . import file_parser
import generic_file_parser from . import generic_file_parser

View File

@@ -21,8 +21,8 @@ from openerp.tools.translate import _
from openerp.osv.orm import except_orm from openerp.osv.orm import except_orm
import tempfile import tempfile
import datetime import datetime
from parser import BankStatementImportParser from .parser import BankStatementImportParser
from parser import UnicodeDictReader from .parser import UnicodeDictReader
try: try:
import xlrd import xlrd
except: except:
@@ -41,12 +41,12 @@ class FileParser(BankStatementImportParser):
""" """
def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None, def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None,
**kwargs): dialect=None, **kwargs):
""" """
:param char: parse_name: The name of the parser :param char: parse_name: The name of the parser
:param char: ftype: extension of the file (could be csv, xls or :param char: ftype: extension of the file (could be csv, xls or
xlsx) xlsx)
:param dict: extra_fields: extra fields to add to the conversion :param dict: extra_fields: extra fields to put into the conversion
dict. In the format {fieldname: fieldtype} dict. In the format {fieldname: fieldtype}
:param list: header : specify header fields if the csv file has no :param list: header : specify header fields if the csv file has no
header header
@@ -58,19 +58,13 @@ class FileParser(BankStatementImportParser):
raise except_orm( raise except_orm(
_('User Error'), _('User Error'),
_('Invalid file type %s. Please use csv, xls or xlsx') % ftype) _('Invalid file type %s. Please use csv, xls or xlsx') % ftype)
self.conversion_dict = { self.conversion_dict = extra_fields
'ref': unicode,
'label': unicode,
'date': datetime.datetime,
'amount': float_or_zero,
}
if extra_fields:
self.conversion_dict.update(extra_fields)
self.keys_to_validate = self.conversion_dict.keys() self.keys_to_validate = self.conversion_dict.keys()
self.fieldnames = header self.fieldnames = header
self._datemode = 0 # used only for xls documents, self._datemode = 0 # used only for xls documents,
# 0 means Windows mode (1900 based dates). # 0 means Windows mode (1900 based dates).
# Set in _parse_xls, from the contents of the file # Set in _parse_xls, from the contents of the file
self.dialect = dialect
def _custom_format(self, *args, **kwargs): def _custom_format(self, *args, **kwargs):
"""No other work on data are needed in this parser.""" """No other work on data are needed in this parser."""
@@ -118,7 +112,8 @@ class FileParser(BankStatementImportParser):
csv_file.write(self.filebuffer) csv_file.write(self.filebuffer)
csv_file.flush() csv_file.flush()
with open(csv_file.name, 'rU') as fobj: with open(csv_file.name, 'rU') as fobj:
reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames) reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames,
dialect=self.dialect)
return list(reader) return list(reader)
def _parse_xls(self): def _parse_xls(self):
@@ -181,9 +176,9 @@ class FileParser(BankStatementImportParser):
except Exception as err: except Exception as err:
raise except_orm( raise except_orm(
_("Date format is not valid"), _("Date format is not valid"),
_("Please modify the cell formatting to date format" _("Please modify the cell formatting to date "
" for column: %s value: %s\n Please check the " "format for column: %s value: %s\n Please check "
"line with ref: %s\n \n Detail: %s") % "the line with ref: %s\n \n Detail: %s") %
(rule, line.get(rule, _('Missing')), (rule, line.get(rule, _('Missing')),
line.get('ref', line), repr(err))) line.get('ref', line), repr(err)))
else: else:

View File

@@ -19,18 +19,31 @@
############################################################################## ##############################################################################
import datetime import datetime
from file_parser import FileParser from .file_parser import FileParser
from openerp.addons.account_statement_base_import.parser.file_parser import (
float_or_zero
)
from openerp.tools import ustr
class GenericFileParser(FileParser): class GenericFileParser(FileParser):
"""Standard parser that use a define format in csv or xls to import into a """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 bank statement. This is mostely an example of how to proceed to create a
parser, but will also be useful as it allow to import a basic flat file. new parser, but will also be useful as it allow to import a basic flat
file.
""" """
def __init__(self, parse_name, ftype='csv', **kwargs): def __init__(self, parse_name, ftype='csv', **kwargs):
conversion_dict = {
'ref': ustr,
'label': ustr,
'date': datetime.datetime,
'amount': float_or_zero,
}
super(GenericFileParser, self).__init__( super(GenericFileParser, self).__init__(
parse_name, ftype=ftype, **kwargs) parse_name, ftype=ftype,
extra_fields=conversion_dict,
**kwargs)
@classmethod @classmethod
def parser_for(cls, parser_name): def parser_for(cls, parser_name):
@@ -42,9 +55,9 @@ class GenericFileParser(FileParser):
def get_st_line_vals(self, line, *args, **kwargs): def get_st_line_vals(self, line, *args, **kwargs):
""" """
This method must return a dict of vals that can be passed to create 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 method of statement line in order to record it. It is the
of every parser to give this dict of vals, so each one can implement his responsibility of every parser to give this dict of vals, so each one
own way of recording the lines. can implement his own way of recording the lines.
:param: line: a dict of vals that represent a line of :param: line: a dict of vals that represent a line of
result_row_list result_row_list
:return: dict of values to give to the create method of statement :return: dict of values to give to the create method of statement

View File

@@ -29,11 +29,16 @@ def UnicodeDictReader(utf8_data, **kwargs):
pos = utf8_data.tell() pos = utf8_data.tell()
sample_data = utf8_data.read(2048) sample_data = utf8_data.read(2048)
utf8_data.seek(pos) utf8_data.seek(pos)
dialect = sniffer.sniff(sample_data, delimiters=',;\t') 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) csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs)
for row in csv_reader: for row in csv_reader:
yield dict([(key, unicode(value, 'utf-8')) for key, value in yield dict([(unicode(key or '', 'utf-8'),
row.iteritems()]) unicode(value or '', 'utf-8'))
for key, value in row.iteritems()])
class BankStatementImportParser(object): class BankStatementImportParser(object):
@@ -48,8 +53,8 @@ class BankStatementImportParser(object):
def __init__(self, profile, *args, **kwargs): def __init__(self, profile, *args, **kwargs):
# The name of the parser as it will be called # The name of the parser as it will be called
self.parser_name = profile.import_type self.parser_name = profile.import_type
# The result as a list of row. One row per line of data in the file, but # The result as a list of row. One row per line of data in the file,
# not the commission one ! # but not the commission one!
self.result_row_list = None self.result_row_list = None
# The file buffer on which to work on # The file buffer on which to work on
self.filebuffer = None self.filebuffer = None
@@ -128,9 +133,9 @@ class BankStatementImportParser(object):
def get_st_line_vals(self, line, *args, **kwargs): def get_st_line_vals(self, line, *args, **kwargs):
"""Implement a method in your parser that must return a dict of vals """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 that can be passed to create method of statement line in order to
it. It is the responsibility of every parser to give this dict of vals, record it. It is the responsibility of every parser to give this dict
so each one can implement his own way of recording the lines. 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 :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, :return: dict of values to give to the create method of statement line,

View File

@@ -23,7 +23,7 @@ import traceback
from openerp.tools.translate import _ from openerp.tools.translate import _
import datetime import datetime
from openerp.osv import fields, orm from openerp.osv import fields, orm
from parser import new_bank_statement_parser from .parser import new_bank_statement_parser
from openerp.tools.config import config from openerp.tools.config import config
@@ -74,13 +74,13 @@ class AccountStatementProfil(orm.Model):
statement ID statement ID
:param: context: global context :param: context: global context
""" """
pass
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines,
context): context):
"""Write the log in the logger """Write the log in the logger
:param int/long statement_id: ID of the concerned account.bank.statement :param int/long statement_id: ID of the concerned
account.bank.statement
:param int/long num_lines: Number of line that have been parsed :param int/long num_lines: Number of line that have been parsed
:return: True :return: True
""" """
@@ -102,7 +102,8 @@ class AccountStatementProfil(orm.Model):
:param dict of vals from parser for account.bank.statement.line :param dict of vals from parser for account.bank.statement.line
(called by parser.get_st_line_vals) (called by parser.get_st_line_vals)
:param int/long statement_id: ID of the concerned account.bank.statement :param int/long statement_id: ID of the concerned
account.bank.statement
:return: dict of vals that will be passed to create method of :return: dict of vals that will be passed to create method of
statement line. statement line.
""" """
@@ -133,7 +134,7 @@ class AccountStatementProfil(orm.Model):
""" """
vals = {'profile_id': profile_id} vals = {'profile_id': profile_id}
vals.update(parser.get_st_vals()) vals.update(parser.get_st_vals())
if not vals.get('balance_start'): if vals.get('balance_start') is None:
# Get starting balance from journal balance if parser doesn't # Get starting balance from journal balance if parser doesn't
# fill this data, simulating the manual flow # fill this data, simulating the manual flow
statement_obj = self.pool['account.bank.statement'] statement_obj = self.pool['account.bank.statement']
@@ -173,8 +174,8 @@ class AccountStatementProfil(orm.Model):
ftype="csv", context=None): ftype="csv", context=None):
"""Create a bank statement with the given profile and parser. It will """Create a bank statement with the given profile and parser. It will
fullfill the bank statement with the values of the file providen, but fullfill the bank statement with the values of the file providen, but
will not complete data (like finding the partner, or the right account). will not complete data (like finding the partner, or the right
This will be done in a second step with the completion rules. account). This will be done in a second step with the completion rules.
:param prof : The profile used to import the file :param prof : The profile used to import the file
:param parser: the parser :param parser: the parser

View File

@@ -17,4 +17,4 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
import import_statement from . import import_statement

View File

@@ -20,6 +20,6 @@
############################################################################### ###############################################################################
"""Account Statement Cancel Line.""" """Account Statement Cancel Line."""
import statement # noqa from . import statement # noqa
import statement_line # noqa from . import statement_line # noqa
import wizard # noqa from . import wizard # noqa

View File

@@ -22,7 +22,7 @@
name: line1 name: line1
statement_id: statement_test statement_id: statement_test
ref: ref1 ref: ref1
date: '2014-01-20' date: !eval "'%s-01-20' %(datetime.now().year)"
amount: 100.0 amount: 100.0
- -
I create a second statement line I create a second statement line
@@ -31,7 +31,7 @@
name: line2 name: line2
statement_id: statement_test statement_id: statement_test
ref: ref2 ref: ref2
date: '2014-01-25' date: !eval "'%s-01-25' %(datetime.now().year)"
amount: 200.0 amount: 200.0
- -
I check that the state of the statement is "Draft" I check that the state of the statement is "Draft"

View File

@@ -23,7 +23,7 @@
name: line11 name: line11
statement_id: statement_test_10 statement_id: statement_test_10
ref: ref11 ref: ref11
date: '2014-01-20' date: !eval "'%s-01-20' %(datetime.now().year)"
amount: 100.0 amount: 100.0
- -
I create a second statement line I create a second statement line
@@ -32,7 +32,7 @@
name: line12 name: line12
statement_id: statement_test_10 statement_id: statement_test_10
ref: ref12 ref: ref12
date: '2014-01-25' date: !eval "'%s-01-25' %(datetime.now().year)"
amount: 200.0 amount: 200.0
- -
Now I confirm only the first statement line Now I confirm only the first statement line

View File

@@ -25,7 +25,7 @@
name: line1 name: line1
statement_id: statement_test3 statement_id: statement_test3
ref: line1 ref: line1
date: '2014-01-20' date: !eval "'%s-01-20' %(datetime.now().year)"
amount: 10.0 amount: 10.0
- -
Now I confirm the statement line. That should not pass the balance check Now I confirm the statement line. That should not pass the balance check

View File

@@ -25,7 +25,7 @@
name: line1 name: line1
statement_id: statement_test4 statement_id: statement_test4
ref: line1 ref: line1
date: '2014-01-20' date: !eval "'%s-01-20' %(datetime.now().year)"
amount: 10.0 amount: 10.0
- -
Now I confirm the statement line Now I confirm the statement line

View File

@@ -20,5 +20,5 @@
############################################################################### ###############################################################################
"""Wizard for asking the confirmation when some move is reconciled.""" """Wizard for asking the confirmation when some move is reconciled."""
import cancel_statement # noqa from . import cancel_statement # noqa
import cancel_statement_line # noqa from . import cancel_statement_line # noqa

View File

@@ -19,4 +19,4 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
import commission from . import commission

View File

@@ -31,9 +31,9 @@
'account_statement_base_import' 'account_statement_base_import'
], ],
'description': """ 'description': """
This module brings commission support to bank statement imports. It computes the This module brings commission support to bank statement imports. It computes
sum of a commission field on each transaction and creates a statement entry for the sum of a commission field on each transaction and creates a statement
it. entry for it.
""", """,
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',
'data': [ 'data': [

View File

@@ -24,7 +24,8 @@ class AccountStatementProfil(orm.Model):
commission_analytic_id = profile.commission_analytic_id.id commission_analytic_id = profile.commission_analytic_id.id
comm_values = { comm_values = {
'name': 'IN ' + _('Commission line'), 'name': 'IN ' + _('Commission line'),
'date': parser.get_st_vals().get('date') or datetime.datetime.now(), 'date': parser.get_st_vals().get('date') or
datetime.datetime.now(),
'amount': global_commission_amount, 'amount': global_commission_amount,
'partner_id': partner_id, 'partner_id': partner_id,
'type': 'general', 'type': 'general',
@@ -32,8 +33,8 @@ class AccountStatementProfil(orm.Model):
'account_id': commission_account_id, 'account_id': commission_account_id,
'ref': 'commission', 'ref': 'commission',
'analytic_account_id': commission_analytic_id, 'analytic_account_id': commission_analytic_id,
# !! We set the already_completed so auto-completion will not update # !! We set the already_completed so auto-completion will not
# those values! # update those values!
'already_completed': True, 'already_completed': True,
} }
st_obj = self.pool['account.bank.statement.line'] st_obj = self.pool['account.bank.statement.line']

View File

@@ -92,7 +92,7 @@ class AccountStatementCompletionRule(orm.Model):
ON ON
st_l.statement_id = s.id st_l.statement_id = s.id
WHERE WHERE
st_l.name ~* l.label (st_l.name ~* l.label OR st_l.ref ~* l.label)
AND AND
l.profile_id = s.profile_id l.profile_id = s.profile_id
AND AND
@@ -125,8 +125,8 @@ class AccountStatementLabel(orm.Model):
'label': fields.char('Bank Statement Label', size=100), 'label': fields.char('Bank Statement Label', size=100),
'account_id': fields.many2one('account.account', 'Account', 'account_id': fields.many2one('account.account', 'Account',
required=True, required=True,
help='Account corresponding to the label ' help='Account corresponding to the '
'for a given partner'), 'label for a given partner'),
'company_id': fields.related('account_id', 'company_id', 'company_id': fields.related('account_id', 'company_id',
type='many2one', type='many2one',
relation='res.company', relation='res.company',

View File

@@ -19,7 +19,7 @@
# #
############################################################################## ##############################################################################
import statement from . import statement
import report from . import report
import account from . import account
import voucher from . import voucher

View File

@@ -45,8 +45,8 @@
Features: Features:
1) Improve the bank statement: allows to define profiles (for each Office or 1) Improve the bank statement: allows to define profiles (for each Office or
Bank). The bank statement will then generate the entries based on some criteria Bank). The bank statement will then generate the entries based on some
chosen in the selected profile. You can setup on the profile: criteria chosen in the selected profile. You can setup on the profile:
- the journal to use - the journal to use
- use balance check or not - use balance check or not

View File

@@ -23,4 +23,4 @@
# #
############################################################################## ##############################################################################
import bank_statement_report from . import bank_statement_report

View File

@@ -19,7 +19,7 @@
# #
############################################################################## ##############################################################################
import openerp.addons.account.account_bank_statement as stat_mod import openerp.addons.account.account_bank_statement as stat_mod
from openerp.osv import fields, orm from openerp.osv import fields, orm, osv
from openerp.tools.translate import _ from openerp.tools.translate import _
@@ -52,8 +52,8 @@ class AccountStatementProfile(orm.Model):
'name': fields.char('Name', required=True), 'name': fields.char('Name', required=True),
'sequence': fields.integer( 'sequence': fields.integer(
'Sequence', 'Sequence',
help="Gives a sequence in lists, the first profile will be used as " help="Gives a sequence in lists, the first profile will be used "
"default"), "as default"),
'partner_id': fields.many2one( 'partner_id': fields.many2one(
'res.partner', 'res.partner',
'Bank/Payment Office partner', 'Bank/Payment Office partner',
@@ -144,7 +144,8 @@ class AccountBankStatement(orm.Model):
profile_obj = self.pool['account.statement.profile'] profile_obj = self.pool['account.statement.profile']
user = user_obj.browse(cr, uid, uid, context=context) user = user_obj.browse(cr, uid, uid, context=context)
profile_ids = profile_obj.search( profile_ids = profile_obj.search(
cr, uid, [('company_id', '=', user.company_id.id)], context=context) cr, uid, [('company_id', '=', user.company_id.id)], context=context
)
return profile_ids[0] if profile_ids else False return profile_ids[0] if profile_ids else False
def _get_statement_from_profile(self, cr, uid, profile_ids, context=None): def _get_statement_from_profile(self, cr, uid, profile_ids, context=None):
@@ -304,7 +305,8 @@ class AccountBankStatement(orm.Model):
line if different from the statement line account ID line if different from the statement line account ID
:param int/long analytic_id: ID of analytic account to put on the :param int/long analytic_id: ID of analytic account to put on the
move line move line
:param int/long partner_id: ID of the partner to put on the move line :param int/long partner_id: ID of the partner to put on the move
line
:return: dict of value to create() the account.move.line :return: dict of value to create() the account.move.line
""" """
if context is None: if context is None:
@@ -420,7 +422,7 @@ class AccountBankStatement(orm.Model):
self.create_move_from_st_line( self.create_move_from_st_line(
cr, uid, st_line.id, company_currency_id, cr, uid, st_line.id, company_currency_id,
st_line_number, context) st_line_number, context)
except orm.except_orm, exc: except (orm.except_orm, osv.except_osv) as exc:
msg = "Line ID %s with ref %s had following error: %s" % ( msg = "Line ID %s with ref %s had following error: %s" % (
st_line.id, st_line.ref, exc.value) st_line.id, st_line.ref, exc.value)
errors_stack.append(msg) errors_stack.append(msg)
@@ -445,7 +447,7 @@ class AccountBankStatement(orm.Model):
def get_account_for_counterpart(self, cr, uid, amount, account_receivable, def get_account_for_counterpart(self, cr, uid, amount, account_receivable,
account_payable): account_payable):
"""For backward compatibility.""" """For backward compatibility."""
account_id, type = self.get_account_and_type_for_counterpart( account_id, account_type = self.get_account_and_type_for_counterpart(
cr, uid, amount, account_receivable, account_payable) cr, uid, amount, account_receivable, account_payable)
return account_id return account_id
@@ -496,10 +498,10 @@ class AccountBankStatement(orm.Model):
self, cr, uid, amount, account_receivable, account_payable, self, cr, uid, amount, account_receivable, account_payable,
partner_id=False): partner_id=False):
""" """
Give the amount, payable and receivable account (that can be found using Give the amount, payable and receivable account (that can be found
get_default_pay_receiv_accounts method) and receive the one to use. This using get_default_pay_receiv_accounts method) and receive the one to
method should be use when there is no other way to know which one to use. This method should be use when there is no other way to know which
take. The rules are: one to take. The rules are:
- If the customer checkbox is checked on the found partner, type and - If the customer checkbox is checked on the found partner, type and
account will be customer and receivable account will be customer and receivable
- If the supplier checkbox is checked on the found partner, type and - If the supplier checkbox is checked on the found partner, type and
@@ -606,7 +608,7 @@ class AccountBankStatementLine(orm.Model):
local_context['account_period_prefer_normal'] = True local_context['account_period_prefer_normal'] = True
try: try:
periods = period_obj.find(cr, uid, dt=date, context=local_context) periods = period_obj.find(cr, uid, dt=date, context=local_context)
except orm.except_orm: except (orm.except_orm, osv.except_osv):
# if no period defined, we are certainly at installation time # if no period defined, we are certainly at installation time
return False return False
return periods and periods[0] or False return periods and periods[0] or False
@@ -617,7 +619,8 @@ class AccountBankStatementLine(orm.Model):
_columns = { _columns = {
# Set them as required + 64 char instead of 32 # Set them as required + 64 char instead of 32
'ref': fields.char('Reference', size=64, required=True), 'ref': fields.char('Reference', size=64, required=True),
'period_id': fields.many2one('account.period', 'Period', required=True), 'period_id': fields.many2one(
'account.period', 'Period', required=True),
} }
_defaults = { _defaults = {
'period_id': _get_period, 'period_id': _get_period,
@@ -732,8 +735,8 @@ class AccountBankStatementLine(orm.Model):
'voucher_id': False}} 'voucher_id': False}}
return {'value': {'type': line_type}} return {'value': {'type': line_type}}
def onchange_type(self, cr, uid, line_id, partner_id, line_type, profile_id, def onchange_type(self, cr, uid, line_id, partner_id, line_type,
context=None): profile_id, context=None):
"""Keep the same features as in standard and call super. If an account """Keep the same features as in standard and call super. If an account
is returned, call the method to compute line values. is returned, call the method to compute line values.
""" """

View File

@@ -82,7 +82,8 @@ if not hasattr(std_pos_session, '_prepare_bank_statement'):
cr, uid, [('type', '=', 'cash')], context=context) cr, uid, [('type', '=', 'cash')], context=context)
if not cashids: if not cashids:
cashids = journal_proxy.search( cashids = journal_proxy.search(
cr, uid, [('journal_user', '=', True)], context=context) cr, uid, [('journal_user', '=', True)],
context=context)
jobj.write( jobj.write(
cr, uid, [pos_config.id], {'journal_ids': [(6, 0, cashids)]}) cr, uid, [pos_config.id], {'journal_ids': [(6, 0, cashids)]})
pos_config = jobj.browse(cr, uid, config_id, context=context) pos_config = jobj.browse(cr, uid, config_id, context=context)

View File

@@ -18,5 +18,5 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
import statement from . import statement
import parser from . import parser

View File

@@ -63,7 +63,8 @@ class AccountBankStatement(orm.Model):
}) })
return res return res
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, def create_move_from_st_line(self, cr, uid, st_line_id,
company_currency_id,
st_line_number, context=None): st_line_number, context=None):
if context is None: if context is None:
context = {} context = {}

View File

@@ -54,7 +54,7 @@ class AccountStatementCompletionRule(Model):
string="Account to set"), string="Account to set"),
} }
def set_account(self, cr, uid, id, st_line, context=None): def set_account(self, cr, uid, account_id, st_line, context=None):
""" """
If line name match regex, update account_id If line name match regex, update account_id
Then, call the generic st_line method to complete other values. Then, call the generic st_line method to complete other values.
@@ -69,7 +69,7 @@ class AccountStatementCompletionRule(Model):
name = st_line['name'] name = st_line['name']
res = {} res = {}
if name: if name:
rule = self.browse(cr, uid, id, context=context) rule = self.browse(cr, uid, account_id, context=context)
if re.match(rule.regex, name): if re.match(rule.regex, name):
res['account_id'] = rule.account_id.id res['account_id'] = rule.account_id.id
return res return res

View File

@@ -87,12 +87,14 @@ class test_regex_account_completion(common.TransactionCase):
"""Test the automatic completion on account """Test the automatic completion on account
""" """
self.prepare() self.prepare()
statement_obj = self.st_obj.browse(self.cr, self.uid, self.statement_id) statement_obj = self.st_obj.browse(
self.cr, self.uid, self.statement_id)
statement_obj.button_auto_completion() statement_obj.button_auto_completion()
statement_line1 = self.st_line_obj.browse( statement_line1 = self.st_line_obj.browse(
self.cr, self.uid, self.statement_line1_id) self.cr, self.uid, self.statement_line1_id)
self.assertEquals(self.account_id, statement_line1.account_id.id, self.assertEquals(self.account_id, statement_line1.account_id.id,
"The account should be the account of the completion") "The account should be the account of the completion"
)
statement_line2 = self.st_line_obj.browse( statement_line2 = self.st_line_obj.browse(
self.cr, self.uid, self.statement_line2_id) self.cr, self.uid, self.statement_line2_id)
self.assertNotEqual(self.account_id, statement_line2.account_id.id, self.assertNotEqual(self.account_id, statement_line2.account_id.id,

View File

@@ -19,4 +19,4 @@
# #
############################################################################## ##############################################################################
import statement from . import statement

View File

@@ -110,7 +110,7 @@ class AccountStatementCompletionRule(Model):
elif len(invoice_id) == 1: elif len(invoice_id) == 1:
invoice = invoice_obj.browse(cr, uid, invoice_id[0], invoice = invoice_obj.browse(cr, uid, invoice_id[0],
context=context) context=context)
res['partner_id'] = invoice.partner_id.id res['partner_id'] = invoice.commercial_partner_id.id
# we want the move to have the same ref than the found # we want the move to have the same ref than the found
# invoice's move, thus it will be easier to link them for the # invoice's move, thus it will be easier to link them for the
# accountants # accountants
@@ -151,7 +151,8 @@ class AccountBankStatement(Model):
:param browse_record st_line: account.bank.statement.line record to :param browse_record st_line: account.bank.statement.line record to
create the move from. create the move from.
:param int/long move_id: ID of the account.move to link the move line :param int/long move_id: ID of the account.move to link the move
line
:param float debit: debit amount of the move line :param float debit: debit amount of the move line
:param float credit: credit amount of the move line :param float credit: credit amount of the move line
:param int/long currency_id: ID of currency of the move line to :param int/long currency_id: ID of currency of the move line to
@@ -162,7 +163,8 @@ class AccountBankStatement(Model):
line if different from the statement line account ID line if different from the statement line account ID
:param int/long analytic_id: ID of analytic account to put on the :param int/long analytic_id: ID of analytic account to put on the
move line move line
:param int/long partner_id: ID of the partner to put on the move line :param int/long partner_id: ID of the partner to put on the move
line
:return: dict of value to create() the account.move.line :return: dict of value to create() the account.move.line
""" """
res = super(AccountBankStatement, self)._prepare_move_line_vals( res = super(AccountBankStatement, self)._prepare_move_line_vals(

View File

@@ -29,7 +29,7 @@
statement_id: statement_transactionid_test1 statement_id: statement_transactionid_test1
transaction_id: XXX66Z transaction_id: XXX66Z
ref: 6 ref: 6
date: '2014-01-06' date: !eval "'%s-01-06' %(datetime.now().year)"
amount: 118.4 amount: 118.4
- -
I run the auto complete I run the auto complete

View File

@@ -18,5 +18,5 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
import parser from . import parser
import statement from . import statement

View File

@@ -35,9 +35,9 @@
the importation of different bank and offices that uses transactionID. the importation of different bank and offices that uses transactionID.
This module allows you to import your bank transactions with a standard .csv This module allows you to import your bank transactions with a standard .csv
or .xls file (you'll find samples in the 'data' folder). It respects the chosen or .xls file (you'll find samples in the 'data' folder). It respects the
profile (model provided by the account_statement_ext module) to generate the chosen profile (model provided by the account_statement_ext module) to
entries. generate the entries.
This module can handle a commission taken by the payment office and has the This module can handle a commission taken by the payment office and has the
following format: following format:
@@ -48,8 +48,8 @@
* amount: amount paid in the currency of the journal used in the importation * amount: amount paid in the currency of the journal used in the importation
profile profile
* commission_amount: amount of the comission for each line * commission_amount: amount of the comission for each line
* label: the comunication given by the payment office, used as communication in * label: the comunication given by the payment office, used as communication
the generated entries. in the generated entries.
""", """,
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',
'installable': False, 'installable': False,

View File

@@ -19,4 +19,4 @@
# #
############################################################################## ##############################################################################
import transactionid_file_parser from . import transactionid_file_parser

View File

@@ -18,7 +18,10 @@
# #
############################################################################## ##############################################################################
import datetime import datetime
from account_statement_base_import.parser.file_parser import FileParser from openerp.tools import ustr
from account_statement_base_import.parser.file_parser import (
FileParser, float_or_zero
)
class TransactionIDFileParser(FileParser): class TransactionIDFileParser(FileParser):
@@ -36,14 +39,15 @@ class TransactionIDFileParser(FileParser):
:param list: header : specify header fields if the csv file has no :param list: header : specify header fields if the csv file has no
header header
""" """
extra_fields = {'transaction_id': unicode} conversion_dict = {
'transaction_id': ustr,
'label': ustr,
'date': datetime.datetime,
'amount': float_or_zero,
}
super(TransactionIDFileParser, self).__init__( super(TransactionIDFileParser, self).__init__(
profile, extra_fields=extra_fields, ftype=ftype, header=header, profile, extra_fields=conversion_dict, ftype=ftype, header=header,
**kwargs) **kwargs)
# ref is replaced by transaction_id thus we delete it from check
self.keys_to_validate = [
k for k in self.keys_to_validate if k != 'ref']
del self.conversion_dict['ref']
@classmethod @classmethod
def parser_for(cls, parser_name): def parser_for(cls, parser_name):
@@ -54,9 +58,9 @@ class TransactionIDFileParser(FileParser):
def get_st_line_vals(self, line, *args, **kwargs): def get_st_line_vals(self, line, *args, **kwargs):
"""This method must return a dict of vals that can be passed to create """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 method of statement line in order to record it. It is the
of every parser to give this dict of vals, so each one can implement his responsibility of every parser to give this dict of vals, so each one
own way of recording the lines. can implement his own way of recording the lines.
:param: line: a dict of vals that represent a line of :param: line: a dict of vals that represent a line of
result_row_list result_row_list
:return: dict of values to give to the create method of statement :return: dict of values to give to the create method of statement

View File

@@ -39,10 +39,10 @@ origin, free reference) and above all, to understand which field will be
copied in the reference field of the move and move lines. copied in the reference field of the move and move lines.
The approach here is to state simple rules with one concern: consistency. The approach here is to state simple rules with one concern: consistency.
The reference of the move lines must be the number of the document at The reference of the move lines must be the number of the document at their
their very origin (number of a sales order, of an external document very origin (number of a sales order, of an external document like a supplier
like a supplier invoice, ...). The goal is for the accountant to be invoice, ...). The goal is for the accountant to be able to trace to the
able to trace to the source document from a ledger). source document from a ledger).
The description of a line should always be... well, a description. Not a number The description of a line should always be... well, a description. Not a number
or a cryptic reference. or a cryptic reference.