mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
First commit to migrate and merge base_import, base_completion and commission
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="bank_statement_completion_rule_2" model="account.statement.completion.rule">
|
||||
<field name="name">Match from line label (based on partner field 'Bank Statement Label')</field>
|
||||
<field name="sequence">60</field>
|
||||
<field name="function_to_call">get_from_label_and_partner_field</field>
|
||||
</record>
|
||||
|
||||
<record id="bank_statement_completion_rule_3" model="account.statement.completion.rule">
|
||||
<field name="name">Match from line label (based on partner name)</field>
|
||||
<field name="sequence">70</field>
|
||||
<field name="function_to_call">get_from_label_and_partner_name</field>
|
||||
</record>
|
||||
|
||||
<record id="bank_statement_completion_rule_4" model="account.statement.completion.rule">
|
||||
<field name="name">Match from line reference (based on Invoice number)</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="function_to_call">get_from_ref_and_invoice</field>
|
||||
</record>
|
||||
|
||||
<record id="bank_statement_completion_rule_5" model="account.statement.completion.rule">
|
||||
<field name="name">Match from line reference (based on Invoice Supplier number)</field>
|
||||
<field name="sequence">45</field>
|
||||
<field name="function_to_call">get_from_ref_and_supplier_invoice</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="bk_view_partner_form" model="ir.ui.view">
|
||||
<field name="name">account_bank_statement_import.view.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="property_account_payable" position="after">
|
||||
<field name="bank_statement_label"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,654 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Nicolas Bessi, Joel Grand-Guillaume
|
||||
# Copyright 2011-2012 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
# TODO replace customer supplier by package constant
|
||||
import traceback
|
||||
import sys
|
||||
import logging
|
||||
import simplejson
|
||||
import inspect
|
||||
import datetime
|
||||
|
||||
import psycopg2
|
||||
|
||||
from collections import defaultdict
|
||||
import re
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from operator import attrgetter
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ErrorTooManyPartner(Exception):
|
||||
""" New Exception definition that is raised when more than one partner is
|
||||
matched by the completion rule.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
class AccountStatementProfil(orm.Model):
|
||||
"""Extend the class to add rules per profile that will match at least the
|
||||
partner, but it could also be used to match other values as well.
|
||||
"""
|
||||
_inherit = "account.statement.profile"
|
||||
|
||||
_columns = {
|
||||
# @Akretion: For now, we don't implement this features, but this would
|
||||
# probably be there: 'auto_completion': fields.text('Auto Completion'),
|
||||
# 'transferts_account_id':fields.many2one('account.account',
|
||||
# 'Transferts Account'),
|
||||
# => You can implement it in a module easily, we design it with your
|
||||
# needs in mind as well!
|
||||
|
||||
'rule_ids': fields.many2many(
|
||||
'account.statement.completion.rule',
|
||||
string='Related statement profiles',
|
||||
rel='as_rul_st_prof_rel'),
|
||||
}
|
||||
|
||||
def _get_rules(self, cr, uid, profile, context=None):
|
||||
if isinstance(profile, (int, long)):
|
||||
prof = self.browse(cr, uid, profile, context=context)
|
||||
else:
|
||||
prof = profile
|
||||
# We need to respect the sequence order
|
||||
return sorted(prof.rule_ids, key=attrgetter('sequence'))
|
||||
|
||||
def _find_values_from_rules(self, cr, uid, calls, line, context=None):
|
||||
"""This method will execute all related rules, in their sequence order,
|
||||
to retrieve all the values returned by the first rules that will match.
|
||||
:param calls: list of lookup function name available in rules
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id: value,
|
||||
|
||||
...}
|
||||
"""
|
||||
if not calls:
|
||||
calls = self._get_rules(
|
||||
cr, uid, line['profile_id'], context=context)
|
||||
rule_obj = self.pool.get('account.statement.completion.rule')
|
||||
for call in calls:
|
||||
method_to_call = getattr(rule_obj, call.function_to_call)
|
||||
if len(inspect.getargspec(method_to_call).args) == 6:
|
||||
result = method_to_call(cr, uid, call.id, line, context)
|
||||
else:
|
||||
result = method_to_call(cr, uid, line, context)
|
||||
if result:
|
||||
result['already_completed'] = True
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
class AccountStatementCompletionRule(orm.Model):
|
||||
"""This will represent all the completion method that we can have to
|
||||
fullfill the bank statement lines. You'll be able to extend them in you own
|
||||
module and choose those to apply for every statement profile.
|
||||
The goal of a rule is to fullfill at least the partner of the line, but
|
||||
if possible also the reference because we'll use it in the reconciliation
|
||||
process. The reference should contain the invoice number or the SO number
|
||||
or any reference that will be matched by the invoice accounting move.
|
||||
"""
|
||||
_name = "account.statement.completion.rule"
|
||||
_order = "sequence asc"
|
||||
|
||||
def _get_functions(self, cr, uid, context=None):
|
||||
"""List of available methods for rules.
|
||||
|
||||
Override this to add you own."""
|
||||
return [
|
||||
('get_from_ref_and_invoice',
|
||||
'From line reference (based on customer invoice number)'),
|
||||
('get_from_ref_and_supplier_invoice',
|
||||
'From line reference (based on supplier invoice number)'),
|
||||
('get_from_label_and_partner_field',
|
||||
'From line label (based on partner field)'),
|
||||
('get_from_label_and_partner_name',
|
||||
'From line label (based on partner name)')
|
||||
]
|
||||
|
||||
def __get_functions(self, cr, uid, context=None):
|
||||
""" Call method which can be inherited """
|
||||
return self._get_functions(cr, uid, context=context)
|
||||
|
||||
_columns = {
|
||||
'sequence': fields.integer('Sequence',
|
||||
help="Lower means parsed first."),
|
||||
'name': fields.char('Name', size=128),
|
||||
'profile_ids': fields.many2many(
|
||||
'account.statement.profile',
|
||||
rel='as_rul_st_prof_rel',
|
||||
string='Related statement profiles'),
|
||||
'function_to_call': fields.selection(__get_functions, 'Method'),
|
||||
}
|
||||
|
||||
def _find_invoice(self, cr, uid, st_line, inv_type, context=None):
|
||||
"""Find invoice related to statement line"""
|
||||
inv_obj = self.pool.get('account.invoice')
|
||||
if inv_type == 'supplier':
|
||||
type_domain = ('in_invoice', 'in_refund')
|
||||
number_field = 'supplier_invoice_number'
|
||||
elif inv_type == 'customer':
|
||||
type_domain = ('out_invoice', 'out_refund')
|
||||
number_field = 'number'
|
||||
else:
|
||||
raise orm.except_orm(
|
||||
_('System error'),
|
||||
_('Invalid invoice type for completion: %') % inv_type)
|
||||
|
||||
inv_id = inv_obj.search(cr, uid,
|
||||
[(number_field, '=', st_line['ref'].strip()),
|
||||
('type', 'in', type_domain)],
|
||||
context=context)
|
||||
if inv_id:
|
||||
if len(inv_id) == 1:
|
||||
inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
|
||||
else:
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'partner while looking on %s invoices') %
|
||||
(st_line['name'], st_line['ref'], inv_type))
|
||||
return inv
|
||||
return False
|
||||
|
||||
def _from_invoice(self, cr, uid, line, inv_type, context):
|
||||
"""Populate statement line values"""
|
||||
if inv_type not in ('supplier', 'customer'):
|
||||
raise orm.except_orm(_('System error'),
|
||||
_('Invalid invoice type for completion: %') %
|
||||
inv_type)
|
||||
res = {}
|
||||
inv = self._find_invoice(cr, uid, line, inv_type, context=context)
|
||||
if inv:
|
||||
partner_id = inv.commercial_partner_id.id
|
||||
res = {'partner_id': partner_id,
|
||||
'account_id': inv.account_id.id,
|
||||
'type': inv_type}
|
||||
override_acc = line['master_account_id']
|
||||
if override_acc:
|
||||
res['account_id'] = override_acc
|
||||
return res
|
||||
|
||||
# Should be private but data are initialised with no update XML
|
||||
def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None):
|
||||
"""Match the partner based on the invoice supplier invoice number and
|
||||
the reference of the statement line. Then, call the generic
|
||||
get_values_for_line method to complete other values. If more than one
|
||||
partner matched, raise the ErrorTooManyPartner error.
|
||||
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id': value,
|
||||
|
||||
...}
|
||||
"""
|
||||
return self._from_invoice(cr, uid, line, 'supplier', context=context)
|
||||
|
||||
# Should be private but data are initialised with no update XML
|
||||
def get_from_ref_and_invoice(self, cr, uid, line, context=None):
|
||||
"""Match the partner based on the invoice number and the reference of
|
||||
the statement line. Then, call the generic get_values_for_line method
|
||||
to complete other values. If more than one partner matched, raise the
|
||||
ErrorTooManyPartner error.
|
||||
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id': value,
|
||||
...}
|
||||
"""
|
||||
return self._from_invoice(cr, uid, line, 'customer', context=context)
|
||||
|
||||
# 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):
|
||||
"""
|
||||
Match the partner based on the label field of the statement line and
|
||||
the text defined in the 'bank_statement_label' field of the partner.
|
||||
Remember that we can have values separated with ; Then, call the
|
||||
generic get_values_for_line method to complete other values. If more
|
||||
than one partner matched, raise the ErrorTooManyPartner error.
|
||||
|
||||
:param dict st_line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id': value,
|
||||
|
||||
...}
|
||||
"""
|
||||
partner_obj = self.pool['res.partner']
|
||||
st_obj = self.pool.get('account.bank.statement.line')
|
||||
res = {}
|
||||
# As we have to iterate on each partner for each line,
|
||||
# we memoize the pair to avoid
|
||||
# to redo computation for each line.
|
||||
# Following code can be done by a single SQL query
|
||||
# but this option is not really maintanable
|
||||
if not context.get('label_memoizer'):
|
||||
context['label_memoizer'] = defaultdict(list)
|
||||
partner_ids = partner_obj.search(
|
||||
cr, uid, [('bank_statement_label', '!=', False)],
|
||||
context=context)
|
||||
line_ids = context.get('line_ids', [])
|
||||
for partner in partner_obj.browse(cr, uid, partner_ids,
|
||||
context=context):
|
||||
vals = '|'.join(
|
||||
re.escape(x.strip())
|
||||
for x in partner.bank_statement_label.split(';'))
|
||||
or_regex = ".*%s.*" % vals
|
||||
sql = ("SELECT id from account_bank_statement_line"
|
||||
" WHERE id in %s"
|
||||
" AND name ~* %s")
|
||||
cr.execute(sql, (line_ids, or_regex))
|
||||
pairs = cr.fetchall()
|
||||
for pair in pairs:
|
||||
context['label_memoizer'][pair[0]].append(partner)
|
||||
if st_line['id'] in context['label_memoizer']:
|
||||
found_partner = context['label_memoizer'][st_line['id']]
|
||||
if len(found_partner) > 1:
|
||||
msg = (_('Line named "%s" (Ref:%s) was matched by more than '
|
||||
'one partner while looking on partner label: %s') %
|
||||
(st_line['name'], st_line['ref'],
|
||||
','.join([x.name for x in found_partner])))
|
||||
raise ErrorTooManyPartner(msg)
|
||||
res['partner_id'] = found_partner[0].id
|
||||
st_vals = st_obj.get_values_for_line(
|
||||
cr, uid, profile_id=st_line['profile_id'],
|
||||
master_account_id=st_line['master_account_id'],
|
||||
partner_id=found_partner[0].id, line_type=False,
|
||||
amount=st_line['amount'] if st_line['amount'] else 0.0,
|
||||
context=context)
|
||||
res.update(st_vals)
|
||||
return res
|
||||
|
||||
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 and
|
||||
the name of the partner. Then, call the generic get_values_for_line
|
||||
method to complete other values. If more than one partner matched,
|
||||
raise the ErrorTooManyPartner error.
|
||||
|
||||
:param dict st_line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id': value,
|
||||
|
||||
...}
|
||||
"""
|
||||
res = {}
|
||||
# We memoize allowed partner
|
||||
if not context.get('partner_memoizer'):
|
||||
context['partner_memoizer'] = tuple(
|
||||
self.pool['res.partner'].search(cr, uid, []))
|
||||
if not context['partner_memoizer']:
|
||||
return res
|
||||
st_obj = self.pool.get('account.bank.statement.line')
|
||||
# The regexp_replace() escapes the name to avoid false positive
|
||||
# example: 'John J. Doe (No 1)' is escaped to 'John J\. Doe \(No 1\)'
|
||||
# See http://stackoverflow.com/a/400316/1504003 for a list of
|
||||
# chars to escape. Postgres is POSIX-ARE, compatible with
|
||||
# POSIX-ERE excepted that '\' must be escaped inside brackets according
|
||||
# to:
|
||||
# http://www.postgresql.org/docs/9.0/static/functions-matching.html
|
||||
# in chapter 9.7.3.6. Limits and Compatibility
|
||||
sql = r"""
|
||||
SELECT id FROM (
|
||||
SELECT id,
|
||||
regexp_matches(%s,
|
||||
regexp_replace(name,'([\.\^\$\*\+\?\(\)\[\{\\\|])', %s,
|
||||
'g'), 'i') AS name_match
|
||||
FROM res_partner
|
||||
WHERE id IN %s)
|
||||
AS res_patner_matcher
|
||||
WHERE name_match IS NOT NULL"""
|
||||
cr.execute(
|
||||
sql, (st_line['name'], r"\\\1", context['partner_memoizer']))
|
||||
result = cr.fetchall()
|
||||
if not result:
|
||||
return res
|
||||
if len(result) > 1:
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'partner while looking on partner by name') %
|
||||
(st_line['name'], st_line['ref']))
|
||||
res['partner_id'] = result[0][0]
|
||||
st_vals = st_obj.get_values_for_line(
|
||||
cr, uid, profile_id=st_line['profile_id'],
|
||||
master_account_id=st_line['master_account_id'],
|
||||
partner_id=res['partner_id'], line_type=False,
|
||||
amount=st_line['amount'] if st_line['amount'] else 0.0,
|
||||
context=context)
|
||||
res.update(st_vals)
|
||||
return res
|
||||
|
||||
|
||||
class AccountStatement(orm.Model):
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
def button_confirm_bank(self, cr, uid, ids, context=None):
|
||||
line_obj = self.pool['account.bank.statement.line']
|
||||
for stat_id in ids:
|
||||
line_without_account = line_obj.search(cr, uid, [
|
||||
['statement_id', '=', stat_id],
|
||||
['account_id', '=', False],
|
||||
], context=context)
|
||||
if line_without_account:
|
||||
stat = self.browse(cr, uid, stat_id, context=context)
|
||||
raise orm.except_orm(
|
||||
_('User error'),
|
||||
_('You should fill all account on the line of the'
|
||||
' statement %s') % stat.name)
|
||||
return super(AccountStatement, self).button_confirm_bank(
|
||||
cr, uid, ids, context=context)
|
||||
|
||||
|
||||
class AccountStatementLine(orm.Model):
|
||||
"""
|
||||
Add sparse field on the statement line to allow to store all the bank infos
|
||||
that are given by a bank/office. You can then add you own in your module.
|
||||
The idea here is to store all bank/office infos in the
|
||||
additionnal_bank_fields serialized field when importing the file. If many
|
||||
values, add a tab in the bank statement line to store your specific one.
|
||||
Have a look in account_statement_base_import module to see how we've done
|
||||
it.
|
||||
"""
|
||||
_inherit = "account.bank.statement.line"
|
||||
_order = "already_completed desc, date asc"
|
||||
|
||||
_columns = {
|
||||
'additionnal_bank_fields': fields.serialized(
|
||||
'Additionnal infos from bank',
|
||||
help="Used by completion and import system. Adds every field that "
|
||||
"is present in your bank/office statement file"),
|
||||
'label': fields.sparse(
|
||||
type='char',
|
||||
string='Label',
|
||||
serialization_field='additionnal_bank_fields',
|
||||
help="Generic field to store a label given from the "
|
||||
"bank/office on which we can base the default/standard "
|
||||
"providen rule."),
|
||||
'already_completed': fields.boolean(
|
||||
"Auto-Completed",
|
||||
help="When this checkbox is ticked, the auto-completion "
|
||||
"process/button will ignore this line."),
|
||||
# Set account_id field as optional by removing required option.
|
||||
'account_id': fields.many2one('account.account', 'Account'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'already_completed': False,
|
||||
}
|
||||
|
||||
def _get_line_values_from_rules(self, cr, uid, line, rules, context=None):
|
||||
"""We'll try to find out the values related to the line based on rules
|
||||
setted on the profile.. We will ignore line for which already_completed
|
||||
is ticked.
|
||||
|
||||
:return:
|
||||
A dict of dict value that can be passed directly to the write
|
||||
method of the statement line or {}. The first dict has statement
|
||||
line ID as a key: {117009: {'partner_id': 100997,
|
||||
'account_id': 489L}}
|
||||
"""
|
||||
profile_obj = self.pool['account.statement.profile']
|
||||
if line.get('already_completed'):
|
||||
return {}
|
||||
# Ask the rule
|
||||
vals = profile_obj._find_values_from_rules(
|
||||
cr, uid, rules, line, context)
|
||||
if vals:
|
||||
vals['id'] = line['id']
|
||||
return vals
|
||||
return {}
|
||||
|
||||
def _get_available_columns(self, statement_store,
|
||||
include_serializable=False):
|
||||
"""Return writeable by SQL columns"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
model_cols = statement_line_obj._columns
|
||||
avail = [
|
||||
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]
|
||||
# add sparse fields..
|
||||
if include_serializable:
|
||||
for k, col in model_cols.iteritems():
|
||||
if k in statement_store[0].keys() and \
|
||||
isinstance(col, fields.sparse) and \
|
||||
col.serialization_field not in keys and \
|
||||
col._type == 'char':
|
||||
keys.append(col.serialization_field)
|
||||
keys.sort()
|
||||
return keys
|
||||
|
||||
def _prepare_insert(self, statement, cols):
|
||||
""" Apply column formating to prepare data for SQL inserting
|
||||
Return a copy of statement
|
||||
"""
|
||||
st_copy = statement
|
||||
for k, col in st_copy.iteritems():
|
||||
if k in cols:
|
||||
st_copy[k] = self._columns[k]._symbol_set[1](col)
|
||||
return st_copy
|
||||
|
||||
def _prepare_manyinsert(self, statement_store, cols):
|
||||
""" Apply column formating to prepare multiple SQL inserts
|
||||
Return a copy of statement_store
|
||||
"""
|
||||
values = []
|
||||
for statement in statement_store:
|
||||
values.append(self._prepare_insert(statement, cols))
|
||||
return values
|
||||
|
||||
def _serialize_sparse_fields(self, cols, statement_store):
|
||||
""" Serialize sparse fields values in the target serialized field
|
||||
Return a copy of statement_store
|
||||
"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
model_cols = statement_line_obj._columns
|
||||
sparse_fields = dict(
|
||||
[(k, col) for k, col in model_cols.iteritems() if isinstance(
|
||||
col, fields.sparse) and col._type == 'char'])
|
||||
values = []
|
||||
for statement in statement_store:
|
||||
to_json_k = set()
|
||||
st_copy = statement.copy()
|
||||
for k, col in sparse_fields.iteritems():
|
||||
if k in st_copy:
|
||||
to_json_k.add(col.serialization_field)
|
||||
serialized = st_copy.setdefault(
|
||||
col.serialization_field, {})
|
||||
serialized[k] = st_copy[k]
|
||||
for k in to_json_k:
|
||||
st_copy[k] = simplejson.dumps(st_copy[k])
|
||||
values.append(st_copy)
|
||||
return values
|
||||
|
||||
def _insert_lines(self, cr, uid, statement_store, context=None):
|
||||
""" Do raw insert into database because ORM is awfully slow
|
||||
when doing batch write. It is a shame that batch function
|
||||
does not exist"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
statement_line_obj.check_access_rule(cr, uid, [], 'create')
|
||||
statement_line_obj.check_access_rights(
|
||||
cr, uid, 'create', raise_exception=True)
|
||||
cols = self._get_available_columns(
|
||||
statement_store, include_serializable=True)
|
||||
statement_store = self._prepare_manyinsert(statement_store, cols)
|
||||
tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
|
||||
sql = "INSERT INTO account_bank_statement_line (%s) " \
|
||||
"VALUES (%s);" % tmp_vals
|
||||
try:
|
||||
cr.executemany(
|
||||
sql, tuple(self._serialize_sparse_fields(cols,
|
||||
statement_store)))
|
||||
except psycopg2.Error as sql_err:
|
||||
cr.rollback()
|
||||
raise orm.except_orm(_("ORM bypass error"),
|
||||
sql_err.pgerror)
|
||||
|
||||
def _update_line(self, cr, uid, vals, context=None):
|
||||
""" Do raw update into database because ORM is awfully slow
|
||||
when cheking security.
|
||||
TODO / WARM: sparse fields are skipped by the method. IOW, if your
|
||||
completion rule update an sparse field, the updated value will never
|
||||
be stored in the database. It would be safer to call the update method
|
||||
from the ORM for records updating this kind of fields.
|
||||
"""
|
||||
cols = self._get_available_columns([vals])
|
||||
vals = self._prepare_insert(vals, cols)
|
||||
tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
|
||||
sql = "UPDATE account_bank_statement_line " \
|
||||
"SET %s where id = %%(id)s;" % tmp_vals
|
||||
try:
|
||||
cr.execute(sql, vals)
|
||||
except psycopg2.Error as sql_err:
|
||||
cr.rollback()
|
||||
raise orm.except_orm(_("ORM bypass error"),
|
||||
sql_err.pgerror)
|
||||
|
||||
|
||||
class AccountBankStatement(orm.Model):
|
||||
"""We add a basic button and stuff to support the auto-completion
|
||||
of the bank statement once line have been imported or manually fullfill.
|
||||
"""
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
_columns = {
|
||||
'completion_logs': fields.text('Completion Log', readonly=True),
|
||||
}
|
||||
|
||||
def write_completion_log(self, cr, uid, stat_id, error_msg,
|
||||
number_imported, context=None):
|
||||
"""Write the log in the completion_logs field of the bank statement to
|
||||
let the user know what have been done. This is an append mode, so we
|
||||
don't overwrite what already recoded.
|
||||
|
||||
:param int/long stat_id: ID of the account.bank.statement
|
||||
:param char error_msg: Message to add
|
||||
:number_imported int/long: Number of lines that have been completed
|
||||
:return True
|
||||
"""
|
||||
user_name = self.pool.get('res.users').read(
|
||||
cr, uid, uid, ['name'], context=context)['name']
|
||||
statement = self.browse(cr, uid, stat_id, context=context)
|
||||
number_line = len(statement.line_ids)
|
||||
log = self.read(cr, uid, stat_id, ['completion_logs'],
|
||||
context=context)['completion_logs']
|
||||
log = log if log else ""
|
||||
completion_date = datetime.datetime.now().strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
message = (_("%s Bank Statement ID %s has %s/%s lines completed by "
|
||||
"%s \n%s\n%s\n") % (completion_date, stat_id,
|
||||
number_imported, number_line,
|
||||
user_name, error_msg, log))
|
||||
self.write(
|
||||
cr, uid, [stat_id], {'completion_logs': message}, context=context)
|
||||
|
||||
body = (_('Statement ID %s auto-completed for %s/%s lines completed') %
|
||||
(stat_id, number_imported, number_line)),
|
||||
self.message_post(cr, uid, [stat_id], body=body, context=context)
|
||||
return True
|
||||
|
||||
def button_auto_completion(self, cr, uid, ids, context=None):
|
||||
"""Complete line with values given by rules and tic the
|
||||
already_completed checkbox so we won't compute them again unless the
|
||||
user untick them!
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
stat_line_obj = self.pool['account.bank.statement.line']
|
||||
profile_obj = self.pool.get('account.statement.profile')
|
||||
compl_lines = 0
|
||||
stat_line_obj.check_access_rule(cr, uid, [], 'create')
|
||||
stat_line_obj.check_access_rights(
|
||||
cr, uid, 'create', raise_exception=True)
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
msg_lines = []
|
||||
ctx = context.copy()
|
||||
ctx['line_ids'] = tuple((x.id for x in stat.line_ids))
|
||||
b_profile = stat.profile_id
|
||||
rules = profile_obj._get_rules(cr, uid, b_profile, context=context)
|
||||
# Only for perfo even it gains almost nothing
|
||||
profile_id = b_profile.id
|
||||
master_account_id = b_profile.receivable_account_id
|
||||
master_account_id = master_account_id.id if \
|
||||
master_account_id else False
|
||||
res = False
|
||||
for line in stat_line_obj.read(cr, uid, ctx['line_ids']):
|
||||
try:
|
||||
# performance trick
|
||||
line['master_account_id'] = master_account_id
|
||||
line['profile_id'] = profile_id
|
||||
res = stat_line_obj._get_line_values_from_rules(
|
||||
cr, uid, line, rules, context=ctx)
|
||||
if res:
|
||||
compl_lines += 1
|
||||
except ErrorTooManyPartner, exc:
|
||||
msg_lines.append(repr(exc))
|
||||
except Exception, exc:
|
||||
msg_lines.append(repr(exc))
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
_logger.error(st)
|
||||
if res:
|
||||
# stat_line_obj.write(cr, uid, [line.id], vals,
|
||||
# context=ctx)
|
||||
try:
|
||||
stat_line_obj._update_line(
|
||||
cr, uid, res, context=context)
|
||||
except Exception as exc:
|
||||
msg_lines.append(repr(exc))
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
_logger.error(st)
|
||||
# we can commit as it is not needed to be atomic
|
||||
# commiting here adds a nice perfo boost
|
||||
if not compl_lines % 500:
|
||||
cr.commit()
|
||||
msg = u'\n'.join(msg_lines)
|
||||
self.write_completion_log(cr, uid, stat.id,
|
||||
msg, compl_lines, context=context)
|
||||
return True
|
||||
@@ -20,4 +20,4 @@
|
||||
##############################################################################
|
||||
from . import parser
|
||||
from . import wizard
|
||||
from . import statement
|
||||
from . import models
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
'category': 'Finance',
|
||||
'complexity': 'normal',
|
||||
'depends': [
|
||||
'account_statement_ext',
|
||||
'account_statement_base_completion'
|
||||
'account'
|
||||
],
|
||||
'description': """
|
||||
This module brings basic methods and fields on bank statement to deal with
|
||||
@@ -62,11 +61,14 @@
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': [
|
||||
"data/completion_rule_data.xml",
|
||||
"wizard/import_statement_view.xml",
|
||||
"statement_view.xml",
|
||||
"views/account_move_view.xml",
|
||||
"views/journal_view.xml",
|
||||
"views/partner_view.xml",
|
||||
],
|
||||
'test': [],
|
||||
'installable': False,
|
||||
'installable': True,
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
|
||||
22
account_statement_base_import/data/completion_rule_data.xml
Normal file
22
account_statement_base_import/data/completion_rule_data.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="bank_statement_completion_rule_2" model="account.move.completion.rule">
|
||||
<field name="name">Match from line label (based on partner field 'Bank Statement Label')</field>
|
||||
<field name="sequence">60</field>
|
||||
<field name="function_to_call">get_from_name_and_partner_field</field>
|
||||
</record>
|
||||
|
||||
<record id="bank_statement_completion_rule_3" model="account.move.completion.rule">
|
||||
<field name="name">Match from line label (based on partner name)</field>
|
||||
<field name="sequence">70</field>
|
||||
<field name="function_to_call">get_from_name_and_partner_name</field>
|
||||
</record>
|
||||
|
||||
<record id="bank_statement_completion_rule_4" model="account.move.completion.rule">
|
||||
<field name="name">Match from line reference (based on Invoice reference)</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="function_to_call">get_from_ref_and_invoice</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
24
account_statement_base_import/models/__init__.py
Normal file
24
account_statement_base_import/models/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Joel Grand-Guillaume
|
||||
# Copyright 2011-2012 Camptocamp SA
|
||||
# Copyright 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>)
|
||||
#
|
||||
# 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 account_journal
|
||||
from . import account_move
|
||||
from . import partner
|
||||
281
account_statement_base_import/models/account_journal.py
Normal file
281
account_statement_base_import/models/account_journal.py
Normal file
@@ -0,0 +1,281 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Joel Grand-Guillaume
|
||||
# Copyright 2011-2012 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import sys
|
||||
import traceback
|
||||
from openerp import _, api, fields, models
|
||||
from ..parser.parser import new_move_parser
|
||||
from openerp.exceptions import UserError, ValidationError
|
||||
from operator import attrgetter
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_name = 'account.journal'
|
||||
_inherit = ['account.journal', 'mail.thread']
|
||||
|
||||
def _get_import_type_selection(self):
|
||||
"""This is the method to be inherited for adding the parser"""
|
||||
return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
|
||||
|
||||
def __get_import_type_selection(self):
|
||||
""" Call method which can be inherited """
|
||||
return self._get_import_type_selection()
|
||||
|
||||
commission_account_id = fields.Many2one(
|
||||
comodel_name='account.account',
|
||||
string='Commission account')
|
||||
|
||||
import_type = fields.Selection(
|
||||
__get_import_type_selection,
|
||||
string='Type of import',
|
||||
default='generic_csvxls_so',
|
||||
required=True,
|
||||
help="Choose here the method by which you want to import bank"
|
||||
"statement for this profile.")
|
||||
|
||||
last_import_date = fields.Datetime(
|
||||
string="Last Import Date")
|
||||
|
||||
launch_import_completion = fields.Boolean(
|
||||
string="Launch completion after import",
|
||||
help="Tic that box to automatically launch the completion "
|
||||
"on each imported file using this profile.")
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
string='Bank/Payment Office partner',
|
||||
help="Put a partner if you want to have it on the commission move "
|
||||
"(and optionaly on the counterpart of the intermediate/"
|
||||
"banking move if you tick the corresponding checkbox).")
|
||||
|
||||
receivable_account_id = fields.Many2one(
|
||||
comodel_name='account.account',
|
||||
string='Force Receivable/Payable Account',
|
||||
help="Choose a receivable account to force the default "
|
||||
"debit/credit account (eg. an intermediat bank account "
|
||||
"instead of default debitors).")
|
||||
|
||||
rule_ids = fields.Many2many(
|
||||
comodel_name='account.move.completion.rule',
|
||||
string='Auto-completion rules',
|
||||
rel='as_rul_st_prof_rel')
|
||||
|
||||
def _get_rules(self):
|
||||
# We need to respect the sequence order
|
||||
return sorted(self.rule_ids, key=attrgetter('sequence'))
|
||||
|
||||
def _find_values_from_rules(self, calls, line):
|
||||
"""This method will execute all related rules, in their sequence order,
|
||||
to retrieve all the values returned by the first rules that will match.
|
||||
:param calls: list of lookup function name available in rules
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id: value,
|
||||
...}
|
||||
"""
|
||||
if not calls:
|
||||
calls = self._get_rules()
|
||||
rule_obj = self.env['account.move.completion.rule']
|
||||
for call in calls:
|
||||
method_to_call = getattr(rule_obj, call.function_to_call)
|
||||
result = method_to_call(line)
|
||||
if result:
|
||||
result['already_completed'] = True
|
||||
return result
|
||||
return None
|
||||
|
||||
@api.multi
|
||||
def _write_extra_move_lines(self, parser, move):
|
||||
"""Insert extra lines after the main statement lines.
|
||||
|
||||
After the main statement lines have been created, you can override this
|
||||
method to create extra statement lines.
|
||||
|
||||
:param: browse_record of the current parser
|
||||
:param: result_row_list: [{'key':value}]
|
||||
:param: profile: browserecord of account.statement.profile
|
||||
:param: statement_id: int/long of the current importing
|
||||
statement ID
|
||||
:param: context: global context
|
||||
"""
|
||||
move_line_obj = self.env['account.move.line']
|
||||
global_commission_amount = 0
|
||||
total_amount = 0
|
||||
for row in parser.result_row_list:
|
||||
global_commission_amount += float(
|
||||
row.get('commission_amount', '0.0'))
|
||||
total_amount += float(
|
||||
row.get('amount', '0.0'))
|
||||
total_amount += global_commission_amount
|
||||
partner_id = self.partner_id.id
|
||||
# Commission line
|
||||
if global_commission_amount < 0.0:
|
||||
commission_account_id = self.commission_account_id.id
|
||||
comm_values = {
|
||||
'name': _('Commission line'),
|
||||
'date_maturity': parser.get_move_vals().get('date') or
|
||||
fields.Date.today(),
|
||||
'debit': -global_commission_amount,
|
||||
'partner_id': partner_id,
|
||||
'move_id': move.id,
|
||||
'account_id': commission_account_id,
|
||||
'already_completed': True,
|
||||
}
|
||||
move_line_obj.with_context(check_move_validity=False).create(comm_values)
|
||||
# Counterpart line
|
||||
if total_amount > 0.0:
|
||||
receivable_account_id = self.receivable_account_id.id or False
|
||||
counterpart_values = {
|
||||
'name': _('Counterpart line'),
|
||||
'date_maturity': parser.get_move_vals().get('date') or
|
||||
fields.Date.today(),
|
||||
'debit': total_amount,
|
||||
'partner_id': partner_id,
|
||||
'move_id': move.id,
|
||||
'account_id': receivable_account_id,
|
||||
'already_completed': True,
|
||||
}
|
||||
move_line_obj.create(counterpart_values)
|
||||
|
||||
@api.multi
|
||||
def write_logs_after_import(self, move, num_lines):
|
||||
"""Write the log in the logger
|
||||
|
||||
:param int/long statement_id: ID of the concerned
|
||||
account.bank.statement
|
||||
:param int/long num_lines: Number of line that have been parsed
|
||||
:return: True
|
||||
"""
|
||||
self.message_post(
|
||||
body=_('Move %s have been imported with %s '
|
||||
'lines.') % (move.name, num_lines))
|
||||
return True
|
||||
|
||||
def prepare_move_line_vals(self, parser_vals, move):
|
||||
"""Hook to build the values of a line from the parser returned values.
|
||||
At least it fullfill the statement_id. Overide it to add your own
|
||||
completion if needed.
|
||||
|
||||
:param dict of vals from parser for account.bank.statement.line
|
||||
(called by parser.get_st_line_vals)
|
||||
:param int/long statement_id: ID of the concerned
|
||||
account.bank.statement
|
||||
:return: dict of vals that will be passed to create method of
|
||||
statement line.
|
||||
"""
|
||||
move_line_obj = self.env['account.move.line']
|
||||
values = parser_vals
|
||||
values['company_id'] = self.company_id.id
|
||||
values['journal_id'] = self.id
|
||||
values['move_id'] = move.id
|
||||
if values['credit'] > 0.0:
|
||||
values['account_id'] = self.default_credit_account_id.id
|
||||
else:
|
||||
values['account_id'] = self.default_debit_account_id.id
|
||||
values = move_line_obj._add_missing_default_values(values)
|
||||
return values
|
||||
|
||||
def prepare_move_vals(self, result_row_list, parser):
|
||||
"""Hook to build the values of the statement from the parser and
|
||||
the profile.
|
||||
"""
|
||||
vals = {'journal_id': self.id}
|
||||
vals.update(parser.get_move_vals())
|
||||
return vals
|
||||
|
||||
def multi_move_import(self, file_stream, ftype="csv"):
|
||||
"""Create multiple bank statements from values given by the parser for
|
||||
the given profile.
|
||||
|
||||
:param int/long profile_id: ID of the profile used to import the file
|
||||
:param filebuffer file_stream: binary of the providen file
|
||||
:param char: ftype represent the file exstension (csv by default)
|
||||
:return: list: list of ids of the created account.bank.statemênt
|
||||
"""
|
||||
parser = new_move_parser(self, ftype=ftype)
|
||||
res = []
|
||||
for result_row_list in parser.parse(file_stream):
|
||||
move = self._move_import(parser, file_stream, ftype=ftype)
|
||||
res.append(move)
|
||||
return res
|
||||
|
||||
def _move_import(self, parser, file_stream, ftype="csv"):
|
||||
"""Create a bank statement with the given profile and parser. It will
|
||||
fullfill the bank statement with the values of the file providen, but
|
||||
will not complete data (like finding the partner, or the right
|
||||
account). This will be done in a second step with the completion rules.
|
||||
|
||||
:param prof : The profile used to import the file
|
||||
:param parser: the parser
|
||||
:param filebuffer file_stream: binary of the providen file
|
||||
:param char: ftype represent the file exstension (csv by default)
|
||||
:return: ID of the created account.bank.statemênt
|
||||
"""
|
||||
move_obj = self.env['account.move']
|
||||
move_line_obj = self.env['account.move.line']
|
||||
attachment_obj = self.env['ir.attachment']
|
||||
result_row_list = parser.result_row_list
|
||||
# Check all key are present in account.bank.statement.line!!
|
||||
if not result_row_list:
|
||||
raise UserError(_("Nothing to import: "
|
||||
"The file is empty"))
|
||||
parsed_cols = parser.get_move_line_vals(result_row_list[0]).keys()
|
||||
for col in parsed_cols:
|
||||
if col not in move_line_obj._columns:
|
||||
raise UserError(
|
||||
_("Missing column! Column %s you try to import is not "
|
||||
"present in the bank statement line!") % col)
|
||||
move_vals = self.prepare_move_vals(result_row_list, parser)
|
||||
move = move_obj.create(move_vals)
|
||||
try:
|
||||
# Record every line in the bank statement
|
||||
move_store = []
|
||||
for line in result_row_list:
|
||||
parser_vals = parser.get_move_line_vals(line)
|
||||
values = self.prepare_move_line_vals(parser_vals, move)
|
||||
move_store.append(values)
|
||||
# Hack to bypass ORM poor perfomance. Sob...
|
||||
move_line_obj._insert_lines(move_store)
|
||||
self._write_extra_move_lines(parser, move)
|
||||
attachment_data = {
|
||||
'name': 'statement file',
|
||||
'datas': file_stream,
|
||||
'datas_fname': "%s.%s" % (fields.Date.today(), ftype),
|
||||
'res_model': 'account.move',
|
||||
'res_id': move.id,
|
||||
}
|
||||
attachment_obj.create(attachment_data)
|
||||
# If user ask to launch completion at end of import, do it!
|
||||
if self.launch_import_completion:
|
||||
move.button_auto_completion()
|
||||
# Write the needed log infos on profile
|
||||
self.write_logs_after_import(move, len(result_row_list))
|
||||
except Exception:
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
raise ValidationError(
|
||||
_("Statement import error"
|
||||
"The statement cannot be created: %s") % st)
|
||||
return move
|
||||
392
account_statement_base_import/models/account_move.py
Normal file
392
account_statement_base_import/models/account_move.py
Normal file
@@ -0,0 +1,392 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Nicolas Bessi, Joel Grand-Guillaume
|
||||
# Copyright 2011-2012 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
# TODO replace customer supplier by package constant
|
||||
import traceback
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import psycopg2
|
||||
|
||||
from openerp import _, api, fields, models
|
||||
from openerp.exceptions import ValidationError
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ErrorTooManyPartner(Exception):
|
||||
""" New Exception definition that is raised when more than one partner is
|
||||
matched by the completion rule.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
class AccountMoveCompletionRule(models.Model):
|
||||
"""This will represent all the completion method that we can have to
|
||||
fullfill the bank statement lines. You'll be able to extend them in you own
|
||||
module and choose those to apply for every statement profile.
|
||||
The goal of a rule is to fullfill at least the partner of the line, but
|
||||
if possible also the reference because we'll use it in the reconciliation
|
||||
process. The reference should contain the invoice number or the SO number
|
||||
or any reference that will be matched by the invoice accounting move.
|
||||
"""
|
||||
_name = "account.move.completion.rule"
|
||||
_order = "sequence asc"
|
||||
|
||||
def _get_functions(self):
|
||||
"""List of available methods for rules.
|
||||
|
||||
Override this to add you own."""
|
||||
return [
|
||||
('get_from_ref_and_invoice',
|
||||
'From line reference (based on invoice reference)'),
|
||||
('get_from_name_and_partner_field',
|
||||
'From line name (based on partner field)'),
|
||||
('get_from_name_and_partner_name',
|
||||
'From line name (based on partner name)')
|
||||
]
|
||||
|
||||
def __get_functions(self):
|
||||
""" Call method which can be inherited """
|
||||
return self._get_functions()
|
||||
|
||||
sequence = fields.Integer(
|
||||
string='Sequence',
|
||||
help="Lower means parsed first.")
|
||||
name = fields.Char(
|
||||
string='Name',
|
||||
size=128)
|
||||
journal_ids = fields.Many2many(
|
||||
comodel_name='account.journal',
|
||||
rel='as_rul_st_prof_rel',
|
||||
string='Related journals')
|
||||
function_to_call = fields.Selection(
|
||||
__get_functions,
|
||||
string='Method')
|
||||
|
||||
# Should be private but data are initialised with no update XML
|
||||
def get_from_ref_and_invoice(self, line):
|
||||
"""Match the partner based on the invoice number and the reference of
|
||||
the statement line. Then, call the generic get_values_for_line method
|
||||
to complete other values. If more than one partner matched, raise the
|
||||
ErrorTooManyPartner error.
|
||||
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id': value,
|
||||
...}
|
||||
"""
|
||||
res = {}
|
||||
inv_obj = self.env['account.invoice']
|
||||
|
||||
invoices = inv_obj.search([('reference', '=', line.ref.strip())])
|
||||
if invoices:
|
||||
if len(invoices) == 1:
|
||||
invoice = invoices[0]
|
||||
partner_id = invoice.commercial_partner_id.id
|
||||
res = {'partner_id': partner_id}
|
||||
else:
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'partner while looking on invoices') %
|
||||
(line.name, line.ref))
|
||||
return res
|
||||
|
||||
# Should be private but data are initialised with no update XML
|
||||
def get_from_name_and_partner_field(self, line):
|
||||
"""
|
||||
Match the partner based on the label field of the statement line and
|
||||
the text defined in the 'bank_statement_label' field of the partner.
|
||||
Remember that we can have values separated with ; Then, call the
|
||||
generic get_values_for_line method to complete other values. If more
|
||||
than one partner matched, raise the ErrorTooManyPartner error.
|
||||
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id': value,
|
||||
|
||||
...}
|
||||
"""
|
||||
res = {}
|
||||
partner_obj = self.env['res.partner']
|
||||
or_regex = ".*; *%s *;.*" % line.name
|
||||
sql = ("SELECT id from res_partner"
|
||||
" WHERE bank_statement_label ~* %s")
|
||||
self.env.cr.execute(sql, (or_regex, ))
|
||||
partner_ids = self.env.cr.fetchall()
|
||||
partners = partner_obj.browse([x[0] for x in partner_ids])
|
||||
if partners:
|
||||
if len(partners) > 1:
|
||||
msg = (_('Line named "%s" (Ref:%s) was matched by more than '
|
||||
'one partner while looking on partner label: %s') %
|
||||
(line.name, line.ref,
|
||||
','.join([x.name for x in partners])))
|
||||
raise ErrorTooManyPartner(msg)
|
||||
res['partner_id'] = partners[0].id
|
||||
return res
|
||||
|
||||
def get_from_name_and_partner_name(self, line):
|
||||
"""Match the partner based on the label field of the statement line and
|
||||
the name of the partner. Then, call the generic get_values_for_line
|
||||
method to complete other values. If more than one partner matched,
|
||||
raise the ErrorTooManyPartner error.
|
||||
|
||||
:param dict st_line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id': value,
|
||||
|
||||
...}
|
||||
"""
|
||||
res = {}
|
||||
# The regexp_replace() escapes the name to avoid false positive
|
||||
# example: 'John J. Doe (No 1)' is escaped to 'John J\. Doe \(No 1\)'
|
||||
# See http://stackoverflow.com/a/400316/1504003 for a list of
|
||||
# chars to escape. Postgres is POSIX-ARE, compatible with
|
||||
# POSIX-ERE excepted that '\' must be escaped inside brackets according
|
||||
# to:
|
||||
# http://www.postgresql.org/docs/9.0/static/functions-matching.html
|
||||
# in chapter 9.7.3.6. Limits and Compatibility
|
||||
sql = r"""
|
||||
SELECT id FROM (
|
||||
SELECT id,
|
||||
regexp_matches(%s,
|
||||
regexp_replace(name,'([\.\^\$\*\+\?\(\)\[\{\\\|])', %s,
|
||||
'g'), 'i') AS name_match
|
||||
FROM res_partner)
|
||||
AS res_partner_matcher
|
||||
WHERE name_match IS NOT NULL"""
|
||||
self.env.cr.execute(sql, (line.name, r"\\\1"))
|
||||
result = self.env.cr.fetchall()
|
||||
if result:
|
||||
if len(result) > 1:
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'partner while looking on partner by name') %
|
||||
(line.name, line.ref))
|
||||
res['partner_id'] = result[0][0]
|
||||
return res
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
"""
|
||||
Add sparse field on the statement line to allow to store all the bank infos
|
||||
that are given by a bank/office. You can then add you own in your module.
|
||||
The idea here is to store all bank/office infos in the
|
||||
additionnal_bank_fields serialized field when importing the file. If many
|
||||
values, add a tab in the bank statement line to store your specific one.
|
||||
Have a look in account_statement_base_import module to see how we've done
|
||||
it.
|
||||
"""
|
||||
_inherit = "account.move.line"
|
||||
_order = "already_completed desc, date asc"
|
||||
|
||||
already_completed = fields.Boolean(
|
||||
string="Auto-Completed",
|
||||
default=False,
|
||||
help="When this checkbox is ticked, the auto-completion "
|
||||
"process/button will ignore this line.")
|
||||
|
||||
def _get_line_values_from_rules(self, line, rules):
|
||||
"""We'll try to find out the values related to the line based on rules
|
||||
setted on the profile.. We will ignore line for which already_completed
|
||||
is ticked.
|
||||
|
||||
:return:
|
||||
A dict of dict value that can be passed directly to the write
|
||||
method of the statement line or {}. The first dict has statement
|
||||
line ID as a key: {117009: {'partner_id': 100997,
|
||||
'account_id': 489L}}
|
||||
"""
|
||||
journal_obj = self.env['account.journal']
|
||||
if not line.already_completed:
|
||||
# Ask the rule
|
||||
vals = journal_obj._find_values_from_rules(rules, line)
|
||||
if vals:
|
||||
vals['id'] = line['id']
|
||||
return vals
|
||||
return {}
|
||||
|
||||
def _get_available_columns(self, move_store):
|
||||
"""Return writeable by SQL columns"""
|
||||
model_cols = self._columns
|
||||
avail = [
|
||||
k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')
|
||||
]
|
||||
keys = [k for k in move_store[0].keys() if k in avail]
|
||||
keys.sort()
|
||||
return keys
|
||||
|
||||
def _prepare_insert(self, move, cols):
|
||||
""" Apply column formating to prepare data for SQL inserting
|
||||
Return a copy of statement
|
||||
"""
|
||||
move_copy = move
|
||||
for k, col in move_copy.iteritems():
|
||||
if k in cols:
|
||||
move_copy[k] = self._columns[k]._symbol_set[1](col)
|
||||
return move_copy
|
||||
|
||||
def _prepare_manyinsert(self, move_store, cols):
|
||||
""" Apply column formating to prepare multiple SQL inserts
|
||||
Return a copy of statement_store
|
||||
"""
|
||||
values = []
|
||||
for move in move_store:
|
||||
values.append(self._prepare_insert(move, cols))
|
||||
return values
|
||||
|
||||
def _insert_lines(self, move_store):
|
||||
""" Do raw insert into database because ORM is awfully slow
|
||||
when doing batch write. It is a shame that batch function
|
||||
does not exist"""
|
||||
self.check_access_rule('create')
|
||||
self.check_access_rights('create', raise_exception=True)
|
||||
cols = self._get_available_columns(move_store)
|
||||
move_store = self._prepare_manyinsert(move_store, cols)
|
||||
tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
|
||||
sql = "INSERT INTO account_move_line (%s) " \
|
||||
"VALUES (%s);" % tmp_vals
|
||||
try:
|
||||
self.env.cr.executemany(sql, tuple(move_store))
|
||||
except psycopg2.Error as sql_err:
|
||||
self.env.cr.rollback()
|
||||
raise ValidationError(_("ORM bypass error"),
|
||||
sql_err.pgerror)
|
||||
|
||||
def _update_line(self, vals):
|
||||
""" Do raw update into database because ORM is awfully slow
|
||||
when cheking security.
|
||||
TODO / WARM: sparse fields are skipped by the method. IOW, if your
|
||||
completion rule update an sparse field, the updated value will never
|
||||
be stored in the database. It would be safer to call the update method
|
||||
from the ORM for records updating this kind of fields.
|
||||
"""
|
||||
cols = self._get_available_columns([vals])
|
||||
vals = self._prepare_insert(vals, cols)
|
||||
tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
|
||||
sql = "UPDATE account_move_line " \
|
||||
"SET %s where id = %%(id)s;" % tmp_vals
|
||||
try:
|
||||
self.env.cr.execute(sql, vals)
|
||||
except psycopg2.Error as sql_err:
|
||||
self.env.cr.rollback()
|
||||
raise ValidationError(_("ORM bypass error"),
|
||||
sql_err.pgerror)
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
"""We add a basic button and stuff to support the auto-completion
|
||||
of the bank statement once line have been imported or manually fullfill.
|
||||
"""
|
||||
_name = 'account.move'
|
||||
_inherit = ['account.move', 'mail.thread']
|
||||
|
||||
completion_logs = fields.Text(string='Completion Log', readonly=True)
|
||||
|
||||
def write_completion_log(self, error_msg, number_imported):
|
||||
"""Write the log in the completion_logs field of the bank statement to
|
||||
let the user know what have been done. This is an append mode, so we
|
||||
don't overwrite what already recoded.
|
||||
|
||||
:param int/long stat_id: ID of the account.bank.statement
|
||||
:param char error_msg: Message to add
|
||||
:number_imported int/long: Number of lines that have been completed
|
||||
:return True
|
||||
"""
|
||||
user_name = self.env.user.name
|
||||
number_line = len(self.line_ids)
|
||||
log = self.completion_logs or ""
|
||||
completion_date = fields.Datetime.now()
|
||||
message = (_("%s Account Move %s has %s/%s lines completed by "
|
||||
"%s \n%s\n%s\n") % (completion_date, self.name,
|
||||
number_imported, number_line,
|
||||
user_name, error_msg, log))
|
||||
self.write({'completion_logs': message})
|
||||
|
||||
body = (_('Statement ID %s auto-completed for %s/%s lines completed') %
|
||||
(self.name, number_imported, number_line)),
|
||||
self.message_post(body=body)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def button_auto_completion(self):
|
||||
"""Complete line with values given by rules and tic the
|
||||
already_completed checkbox so we won't compute them again unless the
|
||||
user untick them!
|
||||
"""
|
||||
move_line_obj = self.env['account.move.line']
|
||||
compl_lines = 0
|
||||
move_line_obj.check_access_rule('create')
|
||||
move_line_obj.check_access_rights('create', raise_exception=True)
|
||||
for move in self:
|
||||
msg_lines = []
|
||||
journal = move.journal_id
|
||||
rules = journal._get_rules()
|
||||
res = False
|
||||
for line in move.line_ids:
|
||||
try:
|
||||
res = move_line_obj._get_line_values_from_rules(
|
||||
line, rules)
|
||||
if res:
|
||||
compl_lines += 1
|
||||
except ErrorTooManyPartner, exc:
|
||||
msg_lines.append(repr(exc))
|
||||
except Exception, exc:
|
||||
msg_lines.append(repr(exc))
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
_logger.error(st)
|
||||
if res:
|
||||
try:
|
||||
move_line_obj._update_line(res)
|
||||
except Exception as exc:
|
||||
msg_lines.append(repr(exc))
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
_logger.error(st)
|
||||
# we can commit as it is not needed to be atomic
|
||||
# commiting here adds a nice perfo boost
|
||||
if not compl_lines % 500:
|
||||
self.env.cr.commit()
|
||||
msg = u'\n'.join(msg_lines)
|
||||
self.write_completion_log(msg, compl_lines)
|
||||
return True
|
||||
@@ -19,21 +19,20 @@
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import fields, models
|
||||
|
||||
|
||||
class ResPartner(orm.Model):
|
||||
class ResPartner(models.Model):
|
||||
"""Add a bank label on the partner so that we can use it to match
|
||||
this partner when we found this in a statement line.
|
||||
"""
|
||||
_inherit = 'res.partner'
|
||||
|
||||
_columns = {
|
||||
'bank_statement_label': fields.char(
|
||||
'Bank Statement Label', size=100,
|
||||
help="Enter the various label found on your bank statement "
|
||||
"separated by a ; If one of this label is include in the "
|
||||
"bank statement line, the partner will be automatically "
|
||||
"filled (as long as you use this method/rules in your "
|
||||
"statement profile)."),
|
||||
}
|
||||
bank_statement_label = fields.Char(
|
||||
string='Bank Statement Label',
|
||||
size=100,
|
||||
help="Enter the various label found on your bank statement "
|
||||
"separated by a ; If one of this label is include in the "
|
||||
"bank statement line, the partner will be automatically "
|
||||
"filled (as long as you use this method/rules in your "
|
||||
"statement profile).")
|
||||
@@ -19,7 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from .parser import new_bank_statement_parser
|
||||
from .parser import BankStatementImportParser
|
||||
from .parser import new_move_parser
|
||||
from .parser import AccountMoveImportParser
|
||||
from . import file_parser
|
||||
from . import generic_file_parser
|
||||
|
||||
@@ -18,11 +18,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.exceptions import UserError
|
||||
import tempfile
|
||||
import datetime
|
||||
from .parser import BankStatementImportParser
|
||||
from .parser import UnicodeDictReader
|
||||
from .parser import AccountMoveImportParser, UnicodeDictReader
|
||||
try:
|
||||
import xlrd
|
||||
except:
|
||||
@@ -35,7 +34,7 @@ def float_or_zero(val):
|
||||
return float(val) if val else 0.0
|
||||
|
||||
|
||||
class FileParser(BankStatementImportParser):
|
||||
class FileParser(AccountMoveImportParser):
|
||||
"""Generic abstract class for defining parser for .csv, .xls or .xlsx file
|
||||
format.
|
||||
"""
|
||||
@@ -55,8 +54,7 @@ class FileParser(BankStatementImportParser):
|
||||
if ftype in ('csv', 'xls', 'xlsx'):
|
||||
self.ftype = ftype[0:3]
|
||||
else:
|
||||
raise except_orm(
|
||||
_('User Error'),
|
||||
raise UserError(
|
||||
_('Invalid file type %s. Please use csv, xls or xlsx') % ftype)
|
||||
self.conversion_dict = extra_fields
|
||||
self.keys_to_validate = self.conversion_dict.keys()
|
||||
@@ -96,8 +94,7 @@ class FileParser(BankStatementImportParser):
|
||||
parsed_cols = self.result_row_list[0].keys()
|
||||
for col in self.keys_to_validate:
|
||||
if col not in parsed_cols:
|
||||
raise except_orm(_('Invalid data'),
|
||||
_('Column %s not present in file') % col)
|
||||
raise UserError(_('Column %s not present in file') % col)
|
||||
return True
|
||||
|
||||
def _post(self, *args, **kwargs):
|
||||
@@ -143,9 +140,9 @@ class FileParser(BankStatementImportParser):
|
||||
line[rule] = datetime.datetime.strptime(date_string,
|
||||
'%Y-%m-%d')
|
||||
except ValueError as err:
|
||||
raise except_orm(
|
||||
_("Date format is not valid."),
|
||||
_(" It should be YYYY-MM-DD for column: %s"
|
||||
raise UserError(
|
||||
_("Date format is not valid."
|
||||
" It should be YYYY-MM-DD for column: %s"
|
||||
" value: %s \n \n \n Please check the line with "
|
||||
"ref: %s \n \n Detail: %s") %
|
||||
(rule, line.get(rule, _('Missing')),
|
||||
@@ -154,8 +151,7 @@ class FileParser(BankStatementImportParser):
|
||||
try:
|
||||
line[rule] = conversion_rules[rule](line[rule])
|
||||
except Exception as err:
|
||||
raise except_orm(
|
||||
_('Invalid data'),
|
||||
raise UserError(
|
||||
_("Value %s of column %s is not valid.\n Please "
|
||||
"check the line with ref %s:\n \n Detail: %s") %
|
||||
(line.get(rule, _('Missing')), rule,
|
||||
@@ -174,9 +170,9 @@ class FileParser(BankStatementImportParser):
|
||||
self._datemode)
|
||||
line[rule] = datetime.datetime(*t_tuple)
|
||||
except Exception as err:
|
||||
raise except_orm(
|
||||
_("Date format is not valid"),
|
||||
_("Please modify the cell formatting to date "
|
||||
raise UserError(
|
||||
_("Date format is not valid. "
|
||||
"Please modify the cell formatting to date "
|
||||
"format for column: %s value: %s\n Please check "
|
||||
"the line with ref: %s\n \n Detail: %s") %
|
||||
(rule, line.get(rule, _('Missing')),
|
||||
@@ -185,8 +181,7 @@ class FileParser(BankStatementImportParser):
|
||||
try:
|
||||
line[rule] = conversion_rules[rule](line[rule])
|
||||
except Exception as err:
|
||||
raise except_orm(
|
||||
_('Invalid data'),
|
||||
raise UserError(
|
||||
_("Value %s of column %s is not valid.\n Please "
|
||||
"check the line with ref %s:\n \n Detail: %s") %
|
||||
(line.get(rule, _('Missing')), rule,
|
||||
|
||||
@@ -52,7 +52,7 @@ class GenericFileParser(FileParser):
|
||||
"""
|
||||
return parser_name == 'generic_csvxls_so'
|
||||
|
||||
def get_st_line_vals(self, line, *args, **kwargs):
|
||||
def get_move_line_vals(self, line, *args, **kwargs):
|
||||
"""
|
||||
This method must return a dict of vals that can be passed to create
|
||||
method of statement line in order to record it. It is the
|
||||
@@ -70,10 +70,10 @@ class GenericFileParser(FileParser):
|
||||
'label':value,
|
||||
}
|
||||
"""
|
||||
amount = line.get('amount', 0.0)
|
||||
return {
|
||||
'name': line.get('label', line.get('ref', '/')),
|
||||
'date': line.get('date', datetime.datetime.now().date()),
|
||||
'amount': line.get('amount', 0.0),
|
||||
'ref': line.get('ref', '/'),
|
||||
'label': line.get('label', ''),
|
||||
'date_maturity': line.get('date', datetime.datetime.now().date()),
|
||||
'credit': amount > 0.0 and amount or 0.0,
|
||||
'debit': amount < 0.0 and amount or 0.0,
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
##############################################################################
|
||||
import base64
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from openerp.tools.translate import _
|
||||
from openerp import _, fields
|
||||
|
||||
|
||||
def UnicodeDictReader(utf8_data, **kwargs):
|
||||
@@ -41,7 +40,7 @@ def UnicodeDictReader(utf8_data, **kwargs):
|
||||
for key, value in row.iteritems()])
|
||||
|
||||
|
||||
class BankStatementImportParser(object):
|
||||
class AccountMoveImportParser(object):
|
||||
|
||||
"""
|
||||
Generic abstract class for defining parser for different files and
|
||||
@@ -50,21 +49,19 @@ class BankStatementImportParser(object):
|
||||
from the FileParser instead.
|
||||
"""
|
||||
|
||||
def __init__(self, profile, *args, **kwargs):
|
||||
def __init__(self, journal, *args, **kwargs):
|
||||
# The name of the parser as it will be called
|
||||
self.parser_name = profile.import_type
|
||||
self.parser_name = journal.import_type
|
||||
# The result as a list of row. One row per line of data in the file,
|
||||
# but not the commission one!
|
||||
self.result_row_list = None
|
||||
# The file buffer on which to work on
|
||||
self.filebuffer = None
|
||||
# The profile record to access its parameters in any parser method
|
||||
self.profile = profile
|
||||
self.balance_start = None
|
||||
self.balance_end = None
|
||||
self.statement_name = None
|
||||
self.statement_date = None
|
||||
self.support_multi_statements = False
|
||||
self.journal = journal
|
||||
self.move_date = None
|
||||
self.move_name = None
|
||||
self.move_ref= None
|
||||
|
||||
@classmethod
|
||||
def parser_for(cls, parser_name):
|
||||
@@ -119,19 +116,18 @@ class BankStatementImportParser(object):
|
||||
"""
|
||||
return NotImplementedError
|
||||
|
||||
def get_st_vals(self):
|
||||
def get_move_vals(self):
|
||||
"""This method return a dict of vals that ca be passed to create method
|
||||
of statement.
|
||||
:return: dict of vals that represent additional infos for the statement
|
||||
"""
|
||||
return {
|
||||
'name': self.statement_name or '/',
|
||||
'balance_start': self.balance_start,
|
||||
'balance_end_real': self.balance_end,
|
||||
'date': self.statement_date or datetime.now()
|
||||
'name': self.move_name or '/',
|
||||
'date': self.move_date or fields.Datetime.now(),
|
||||
'ref': self.move_ref or '/'
|
||||
}
|
||||
|
||||
def get_st_line_vals(self, line, *args, **kwargs):
|
||||
def get_move_line_vals(self, line, *args, **kwargs):
|
||||
"""Implement a method in your parser that must return a dict of vals
|
||||
that can be passed to create method of statement line in order to
|
||||
record it. It is the responsibility of every parser to give this dict
|
||||
@@ -165,16 +161,10 @@ class BankStatementImportParser(object):
|
||||
raise Exception(_('No buffer file given.'))
|
||||
self._format(*args, **kwargs)
|
||||
self._pre(*args, **kwargs)
|
||||
if self.support_multi_statements:
|
||||
while self._parse(*args, **kwargs):
|
||||
self._validate(*args, **kwargs)
|
||||
self._post(*args, **kwargs)
|
||||
yield self.result_row_list
|
||||
else:
|
||||
self._parse(*args, **kwargs)
|
||||
self._validate(*args, **kwargs)
|
||||
self._post(*args, **kwargs)
|
||||
yield self.result_row_list
|
||||
self._parse(*args, **kwargs)
|
||||
self._validate(*args, **kwargs)
|
||||
self._post(*args, **kwargs)
|
||||
yield self.result_row_list
|
||||
|
||||
|
||||
def itersubclasses(cls, _seen=None):
|
||||
@@ -218,13 +208,13 @@ def itersubclasses(cls, _seen=None):
|
||||
yield sub
|
||||
|
||||
|
||||
def new_bank_statement_parser(profile, *args, **kwargs):
|
||||
def new_move_parser(journal, *args, **kwargs):
|
||||
"""Return an instance of the good parser class based on the given profile.
|
||||
|
||||
:param profile: browse_record of import profile.
|
||||
:return: class instance for given profile import type.
|
||||
"""
|
||||
for cls in itersubclasses(BankStatementImportParser):
|
||||
if cls.parser_for(profile.import_type):
|
||||
return cls(profile, *args, **kwargs)
|
||||
for cls in itersubclasses(AccountMoveImportParser):
|
||||
if cls.parser_for(journal.import_type):
|
||||
return cls(journal, *args, **kwargs)
|
||||
raise ValueError
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Joel Grand-Guillaume
|
||||
# Copyright 2011-2012 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import sys
|
||||
import traceback
|
||||
from openerp.tools.translate import _
|
||||
import datetime
|
||||
from openerp.osv import fields, orm
|
||||
from .parser import new_bank_statement_parser
|
||||
from openerp.tools.config import config
|
||||
|
||||
|
||||
class AccountStatementProfil(orm.Model):
|
||||
_inherit = "account.statement.profile"
|
||||
|
||||
def _get_import_type_selection(self, cr, uid, context=None):
|
||||
"""This is the method to be inherited for adding the parser"""
|
||||
return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
|
||||
|
||||
def __get_import_type_selection(self, cr, uid, context=None):
|
||||
""" Call method which can be inherited """
|
||||
return self._get_import_type_selection(cr, uid, context=context)
|
||||
|
||||
_columns = {
|
||||
'launch_import_completion': fields.boolean(
|
||||
"Launch completion after import",
|
||||
help="Tic that box to automatically launch the completion "
|
||||
"on each imported file using this profile."),
|
||||
'last_import_date': fields.datetime("Last Import Date"),
|
||||
# we remove deprecated as it floods logs in standard/warning level
|
||||
# sob...
|
||||
'rec_log': fields.text('log', readonly=True), # Deprecated
|
||||
'import_type': fields.selection(
|
||||
__get_import_type_selection,
|
||||
'Type of import',
|
||||
required=True,
|
||||
help="Choose here the method by which you want to import bank"
|
||||
"statement for this profile."),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'import_type': 'generic_csvxls_so'
|
||||
}
|
||||
|
||||
def _write_extra_statement_lines(
|
||||
self, cr, uid, parser, result_row_list, profile, statement_id,
|
||||
context):
|
||||
"""Insert extra lines after the main statement lines.
|
||||
|
||||
After the main statement lines have been created, you can override this
|
||||
method to create extra statement lines.
|
||||
|
||||
:param: browse_record of the current parser
|
||||
:param: result_row_list: [{'key':value}]
|
||||
:param: profile: browserecord of account.statement.profile
|
||||
:param: statement_id: int/long of the current importing
|
||||
statement ID
|
||||
:param: context: global context
|
||||
"""
|
||||
|
||||
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines,
|
||||
context):
|
||||
"""Write the log in the logger
|
||||
|
||||
:param int/long statement_id: ID of the concerned
|
||||
account.bank.statement
|
||||
:param int/long num_lines: Number of line that have been parsed
|
||||
:return: True
|
||||
"""
|
||||
self.message_post(
|
||||
cr, uid, ids,
|
||||
body=_('Statement ID %s have been imported with %s '
|
||||
'lines.') % (statement_id, num_lines), context=context)
|
||||
return True
|
||||
|
||||
# Deprecated remove on V8
|
||||
def prepare_statetement_lines_vals(self, *args, **kwargs):
|
||||
return self.prepare_statement_lines_vals(*args, **kwargs)
|
||||
|
||||
def prepare_statement_lines_vals(self, cr, uid, parser_vals,
|
||||
statement_id, context):
|
||||
"""Hook to build the values of a line from the parser returned values.
|
||||
At least it fullfill the statement_id. Overide it to add your own
|
||||
completion if needed.
|
||||
|
||||
:param dict of vals from parser for account.bank.statement.line
|
||||
(called by parser.get_st_line_vals)
|
||||
:param int/long statement_id: ID of the concerned
|
||||
account.bank.statement
|
||||
:return: dict of vals that will be passed to create method of
|
||||
statement line.
|
||||
"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
values = parser_vals
|
||||
values['statement_id'] = statement_id
|
||||
date = values.get('date')
|
||||
period_memoizer = context.get('period_memoizer')
|
||||
if not period_memoizer:
|
||||
period_memoizer = {}
|
||||
context['period_memoizer'] = period_memoizer
|
||||
if period_memoizer.get(date):
|
||||
values['period_id'] = period_memoizer[date]
|
||||
else:
|
||||
# This is awfully slow...
|
||||
periods = self.pool.get('account.period').find(
|
||||
cr, uid, dt=values.get('date'), context=context)
|
||||
values['period_id'] = periods[0]
|
||||
period_memoizer[date] = periods[0]
|
||||
values = statement_line_obj._add_missing_default_values(
|
||||
cr, uid, values, context)
|
||||
return values
|
||||
|
||||
def prepare_statement_vals(self, cr, uid, profile_id, result_row_list,
|
||||
parser, context=None):
|
||||
"""Hook to build the values of the statement from the parser and
|
||||
the profile.
|
||||
"""
|
||||
vals = {'profile_id': profile_id}
|
||||
vals.update(parser.get_st_vals())
|
||||
if vals.get('balance_start') is None:
|
||||
# Get starting balance from journal balance if parser doesn't
|
||||
# fill this data, simulating the manual flow
|
||||
statement_obj = self.pool['account.bank.statement']
|
||||
profile = self.browse(cr, uid, profile_id, context=context)
|
||||
temp = statement_obj.onchange_journal_id(
|
||||
cr, uid, None, profile.journal_id.id, context=context)
|
||||
vals['balance_start'] = temp['value'].get('balance_start', False)
|
||||
return vals
|
||||
|
||||
def multi_statement_import(self, cr, uid, ids, profile_id, file_stream,
|
||||
ftype="csv", context=None):
|
||||
"""Create multiple bank statements from values given by the parser for
|
||||
the given profile.
|
||||
|
||||
:param int/long profile_id: ID of the profile used to import the file
|
||||
:param filebuffer file_stream: binary of the providen file
|
||||
:param char: ftype represent the file exstension (csv by default)
|
||||
:return: list: list of ids of the created account.bank.statemênt
|
||||
"""
|
||||
prof_obj = self.pool['account.statement.profile']
|
||||
if not profile_id:
|
||||
raise orm.except_orm(
|
||||
_("No Profile!"),
|
||||
_("You must provide a valid profile to import a bank "
|
||||
"statement!"))
|
||||
prof = prof_obj.browse(cr, uid, profile_id, context=context)
|
||||
parser = new_bank_statement_parser(prof, ftype=ftype)
|
||||
res = []
|
||||
for result_row_list in parser.parse(file_stream):
|
||||
statement_id = self._statement_import(
|
||||
cr, uid, ids, prof, parser, file_stream, ftype=ftype,
|
||||
context=context)
|
||||
res.append(statement_id)
|
||||
return res
|
||||
|
||||
def _statement_import(self, cr, uid, ids, prof, parser, file_stream,
|
||||
ftype="csv", context=None):
|
||||
"""Create a bank statement with the given profile and parser. It will
|
||||
fullfill the bank statement with the values of the file providen, but
|
||||
will not complete data (like finding the partner, or the right
|
||||
account). This will be done in a second step with the completion rules.
|
||||
|
||||
:param prof : The profile used to import the file
|
||||
:param parser: the parser
|
||||
:param filebuffer file_stream: binary of the providen file
|
||||
:param char: ftype represent the file exstension (csv by default)
|
||||
:return: ID of the created account.bank.statemênt
|
||||
"""
|
||||
statement_obj = self.pool['account.bank.statement']
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
attachment_obj = self.pool['ir.attachment']
|
||||
result_row_list = parser.result_row_list
|
||||
# Check all key are present in account.bank.statement.line!!
|
||||
if not result_row_list:
|
||||
raise orm.except_orm(_("Nothing to import"),
|
||||
_("The file is empty"))
|
||||
parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
|
||||
for col in parsed_cols:
|
||||
if col not in statement_line_obj._columns:
|
||||
raise orm.except_orm(
|
||||
_("Missing column!"),
|
||||
_("Column %s you try to import is not present in the bank "
|
||||
"statement line!") % col)
|
||||
statement_vals = self.prepare_statement_vals(
|
||||
cr, uid, prof.id, result_row_list, parser, context)
|
||||
statement_id = statement_obj.create(
|
||||
cr, uid, statement_vals, context=context)
|
||||
try:
|
||||
# Record every line in the bank statement
|
||||
statement_store = []
|
||||
for line in result_row_list:
|
||||
parser_vals = parser.get_st_line_vals(line)
|
||||
values = self.prepare_statement_lines_vals(
|
||||
cr, uid, parser_vals, statement_id,
|
||||
context)
|
||||
statement_store.append(values)
|
||||
# Hack to bypass ORM poor perfomance. Sob...
|
||||
statement_line_obj._insert_lines(
|
||||
cr, uid, statement_store, context=context)
|
||||
self._write_extra_statement_lines(
|
||||
cr, uid, parser, result_row_list, prof, statement_id, context)
|
||||
# Trigger store field computation if someone has better idea
|
||||
start_bal = statement_obj.read(
|
||||
cr, uid, statement_id, ['balance_start'], context=context)
|
||||
start_bal = start_bal['balance_start']
|
||||
statement_obj.write(
|
||||
cr, uid, [statement_id], {'balance_start': start_bal})
|
||||
attachment_data = {
|
||||
'name': 'statement file',
|
||||
'datas': file_stream,
|
||||
'datas_fname': "%s.%s" % (datetime.datetime.now().date(),
|
||||
ftype),
|
||||
'res_model': 'account.bank.statement',
|
||||
'res_id': statement_id,
|
||||
}
|
||||
attachment_obj.create(cr, uid, attachment_data, context=context)
|
||||
# If user ask to launch completion at end of import, do it!
|
||||
if prof.launch_import_completion:
|
||||
statement_obj.button_auto_completion(
|
||||
cr, uid, [statement_id], context)
|
||||
# Write the needed log infos on profile
|
||||
self.write_logs_after_import(cr, uid, prof.id,
|
||||
statement_id,
|
||||
len(result_row_list),
|
||||
context)
|
||||
except Exception:
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
# TODO we should catch correctly the exception with a python
|
||||
# Exception and only re-catch some special exception.
|
||||
# For now we avoid re-catching error in debug mode
|
||||
if config['debug_mode']:
|
||||
raise
|
||||
raise orm.except_orm(_("Statement import error"),
|
||||
_("The statement cannot be created: %s") % st)
|
||||
return statement_id
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
|
||||
<record id="statement_importer_view_form" model="ir.ui.view">
|
||||
<field name="name">account.statement.profile.view</field>
|
||||
<field name="model">account.statement.profile</field>
|
||||
<field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="bank_statement_prefix" position="after">
|
||||
<separator colspan="4" string="Import related infos"/>
|
||||
<field name="launch_import_completion"/>
|
||||
<field name="last_import_date"/>
|
||||
<field name="import_type"/>
|
||||
<button name="%(account_statement_base_import.statement_importer_action)d"
|
||||
string="Import Bank Statement"
|
||||
type="action" icon="gtk-ok"
|
||||
colspan = "2"/>
|
||||
<group attrs="{'invisible': [('rec_log', '=', False)]}">
|
||||
<separator colspan="4" string="Historical Import Logs"/>
|
||||
<field name="rec_log" colspan="4" nolabel="1" />
|
||||
</group>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="bank_statement_view_form" model="ir.ui.view">
|
||||
<field name="name">account_bank_statement.bank_statement.view_form</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account_statement_base_completion.bank_statement_view_form" />
|
||||
<field eval="20" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='line_ids']/form//field[@name='account_id']" position="attributes">
|
||||
<attribute name="attrs">{'required': [('already_completed','=', True)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='line_ids']/tree//field[@name='account_id']" position="attributes">
|
||||
<attribute name="attrs">{'required': [('already_completed','=', True)]}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -20,8 +20,10 @@
|
||||
#
|
||||
#
|
||||
|
||||
from . import test_base_completion
|
||||
from . import test_base_import
|
||||
|
||||
checks = [
|
||||
test_base_completion,
|
||||
test_base_import
|
||||
]
|
||||
|
||||
20
account_statement_base_import/views/account_move_view.xml
Normal file
20
account_statement_base_import/views/account_move_view.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<odoo>
|
||||
<record id="view_move_importer_form" model="ir.ui.view">
|
||||
<field name="name">account.move.view</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<button name="button_cancel" position="after">
|
||||
<button name="button_auto_completion" string="Auto Completion" states='draft' type="object" class="oe_highlight" groups="account.group_account_invoice"/>
|
||||
</button>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='credit']" position="after">
|
||||
<field name="already_completed"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook" position="inside">
|
||||
<page string="Completion Logs" attrs="{'invisible':[('completion_logs','=',False)]}">
|
||||
<field name="completion_logs" colspan="4" nolabel="1" attrs="{'invisible':[('completion_logs','=',False)]}"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
32
account_statement_base_import/views/journal_view.xml
Normal file
32
account_statement_base_import/views/journal_view.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="journal_importer_view_form" model="ir.ui.view">
|
||||
<field name="name">account.journal.view</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.view_account_journal_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<notebook position="inside">
|
||||
<page string="Import related infos">
|
||||
<group>
|
||||
<field name="launch_import_completion"/>
|
||||
<field name="last_import_date"/>
|
||||
<field name="import_type"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="commission_account_id"/>
|
||||
<field name="receivable_account_id"/>
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<button name="%(account_statement_base_import.statement_importer_action)d"
|
||||
string="Import Bank Statement"
|
||||
type="action" icon="gtk-ok"
|
||||
colspan = "2"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Auto-Completion Rules"/>
|
||||
<field name="rule_ids" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
15
account_statement_base_import/views/partner_view.xml
Normal file
15
account_statement_base_import/views/partner_view.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<odoo>
|
||||
|
||||
<record id="bk_view_partner_form" model="ir.ui.view">
|
||||
<field name="name">account_bank_statement_import.view.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="property_account_payable_id" position="after">
|
||||
<field name="bank_statement_label"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -23,98 +23,68 @@
|
||||
Wizard to import financial institute date in bank statement
|
||||
"""
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
|
||||
from openerp.tools.translate import _
|
||||
from openerp import _, api, fields, models
|
||||
import os
|
||||
|
||||
|
||||
class CreditPartnerStatementImporter(orm.TransientModel):
|
||||
class CreditPartnerStatementImporter(models.TransientModel):
|
||||
_name = "credit.statement.import"
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
context = self.env.context.copy()
|
||||
res = {}
|
||||
if (context.get('active_model', False) ==
|
||||
'account.statement.profile' and
|
||||
if (context.get('active_model', False) == 'account.journal' and
|
||||
context.get('active_ids', False)):
|
||||
ids = context['active_ids']
|
||||
assert len(
|
||||
ids) == 1, 'You cannot use this on more than one profile !'
|
||||
res['profile_id'] = ids[0]
|
||||
other_vals = self.onchange_profile_id(
|
||||
cr, uid, [], res['profile_id'], context=context)
|
||||
res.update(other_vals.get('value', {}))
|
||||
assert len(ids) == 1, \
|
||||
'You cannot use this on more than one profile !'
|
||||
res['journal_id'] = ids[0]
|
||||
self.onchange_journal_id(res['journal_id'])
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'profile_id': fields.many2one('account.statement.profile',
|
||||
'Import configuration parameter',
|
||||
required=True),
|
||||
'input_statement': fields.binary('Statement file', required=True),
|
||||
'partner_id': fields.many2one('res.partner',
|
||||
'Credit insitute partner'),
|
||||
'journal_id': fields.many2one('account.journal',
|
||||
'Financial journal to use transaction'),
|
||||
'file_name': fields.char('File Name', size=128),
|
||||
'receivable_account_id': fields.many2one(
|
||||
'account.account', 'Force Receivable/Payable Account'),
|
||||
'force_partner_on_bank': fields.boolean(
|
||||
'Force partner on bank move',
|
||||
help="Tic that box if you want to use the credit insitute partner "
|
||||
"in the counterpart of the treasury/banking move."),
|
||||
'balance_check': fields.boolean(
|
||||
'Balance check',
|
||||
help="Tic that box if you want OpenERP to control the "
|
||||
"start/end balance before confirming a bank statement. "
|
||||
"If don't ticked, no balance control will be done."),
|
||||
}
|
||||
journal_id = fields.Many2one(
|
||||
comodel_name='account.journal',
|
||||
string='Import configuration parameter',
|
||||
required=True)
|
||||
input_statement = fields.Binary(
|
||||
string='Statement file',
|
||||
required=True)
|
||||
partner_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
string='Credit insitute partner')
|
||||
file_name = fields.Char('File Name', size=128)
|
||||
receivable_account_id = fields.Many2one(
|
||||
comodel_name='account.account',
|
||||
string='Force Receivable/Payable Account')
|
||||
commission_account_id = fields.Many2one(
|
||||
comodel_name='account.account',
|
||||
string='Commission account')
|
||||
|
||||
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
|
||||
res = {}
|
||||
if profile_id:
|
||||
c = self.pool["account.statement.profile"].browse(
|
||||
cr, uid, profile_id, context=context)
|
||||
res = {'value':
|
||||
{'partner_id': c.partner_id and c.partner_id.id or False,
|
||||
'journal_id': c.journal_id and c.journal_id.id or False,
|
||||
'receivable_account_id': c.receivable_account_id.id,
|
||||
'force_partner_on_bank': c.force_partner_on_bank,
|
||||
'balance_check': c.balance_check,
|
||||
}
|
||||
}
|
||||
return res
|
||||
@api.multi
|
||||
def onchange_journal_id(self, journal_id):
|
||||
if journal_id:
|
||||
journal = self.env['account.journal'].browse(journal_id)
|
||||
for importer in self:
|
||||
importer.partner_id = journal.partner_id.id
|
||||
importer.receivable_account_id = journal.receivable_account_id.id
|
||||
importer.commission_account_id = journal.commission_account_id.id
|
||||
|
||||
def _check_extension(self, filename):
|
||||
(__, ftype) = os.path.splitext(filename)
|
||||
if not ftype:
|
||||
# We do not use osv exception we do not want to have it logged
|
||||
raise Exception(_('Please use a file with an extention'))
|
||||
raise Exception(_('Please use a file with an extension'))
|
||||
return ftype
|
||||
|
||||
def import_statement(self, cr, uid, req_id, context=None):
|
||||
@api.multi
|
||||
def import_statement(self):
|
||||
"""This Function import credit card agency statement"""
|
||||
context = context or {}
|
||||
if isinstance(req_id, list):
|
||||
req_id = req_id[0]
|
||||
importer = self.browse(cr, uid, req_id, context)
|
||||
ftype = self._check_extension(importer.file_name)
|
||||
context['file_name'] = importer.file_name
|
||||
sid = self.pool.get(
|
||||
'account.statement.profile').multi_statement_import(
|
||||
cr,
|
||||
uid,
|
||||
False,
|
||||
importer.profile_id.id,
|
||||
importer.input_statement,
|
||||
ftype.replace('.', ''),
|
||||
context=context
|
||||
)
|
||||
model_obj = self.pool.get('ir.model.data')
|
||||
action_obj = self.pool.get('ir.actions.act_window')
|
||||
action_id = model_obj.get_object_reference(
|
||||
cr, uid, 'account', 'action_bank_statement_tree')[1]
|
||||
res = action_obj.read(cr, uid, action_id)
|
||||
res['domain'] = res['domain'][:-1] + ",('id', 'in', %s)]" % sid
|
||||
return res
|
||||
for importer in self:
|
||||
journal = importer.journal_id
|
||||
ftype = self._check_extension(importer.file_name)
|
||||
journal.with_context(
|
||||
file_name=importer.file_name).multi_move_import(
|
||||
importer.input_statement,
|
||||
ftype.replace('.', '')
|
||||
)
|
||||
|
||||
@@ -1,41 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="statement_importer_view" model="ir.ui.view">
|
||||
<field name="name">credit.statement.import.config.view</field>
|
||||
<field name="model">credit.statement.import</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import statement">
|
||||
<group colspan="4" >
|
||||
<field name="profile_id" on_change="onchange_profile_id(profile_id)"/>
|
||||
<field name="input_statement" filename="file_name" colspan="2"/>
|
||||
<field name="file_name" colspan="2" invisible="1"/>
|
||||
<separator string="Import Parameters Summary" colspan="4"/>
|
||||
<field name="partner_id" readonly="1"/>
|
||||
<field name="journal_id" readonly="1"/>
|
||||
<field name="receivable_account_id" readonly="1"/>
|
||||
<field name="force_partner_on_bank" readonly="1"/>
|
||||
<field name="balance_check" readonly="1"/>
|
||||
</group>
|
||||
<separator string="" colspan="4"/>
|
||||
<group colspan="4" col="6">
|
||||
<button icon="gtk-cancel" special="cancel" string="Cancel"/>
|
||||
<button icon="gtk-ok" name="import_statement" string="Import statement" type="object"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<odoo>
|
||||
<record id="statement_importer_view" model="ir.ui.view">
|
||||
<field name="name">credit.statement.import.config.view</field>
|
||||
<field name="model">credit.statement.import</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import statement">
|
||||
<group colspan="4" >
|
||||
<field name="journal_id" on_change="onchange_journal_id(journal_id)"/>
|
||||
<field name="input_statement" filename="file_name" colspan="2"/>
|
||||
<field name="file_name" colspan="2" invisible="1"/>
|
||||
<separator string="Import Parameters Summary" colspan="4"/>
|
||||
<field name="partner_id" readonly="1"/>
|
||||
<field name="receivable_account_id" readonly="1"/>
|
||||
<field name="commission_account_id" readonly="1"/>
|
||||
</group>
|
||||
<separator string="" colspan="4"/>
|
||||
<group colspan="4" col="6">
|
||||
<button icon="gtk-cancel" special="cancel" string="Cancel"/>
|
||||
<button icon="gtk-ok" name="import_statement" string="Import statement" type="object"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="statement_importer_action" model="ir.actions.act_window">
|
||||
<field name="name">Import statement</field>
|
||||
<field name="res_model">credit.statement.import</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="statement_importer_view"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="statement_importer_menu" name="Import Bank Statement" action="statement_importer_action" parent="account.menu_finance_bank_and_cash"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
<record id="statement_importer_action" model="ir.actions.act_window">
|
||||
<field name="name">Import statement</field>
|
||||
<field name="res_model">credit.statement.import</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="statement_importer_view"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user