-
-
- ## date
-
${_('Reference')}
- ## period
-
${_('Partenaire')}
- ## move
-
${_('Montant')}
- ## journal
+
+
+
+
+ ## date
+
${_('Reference')}
+ ## period
+
${_('Partenaire')}
+ ## move
+
${_('Montant')}
+ ## journal
+
+
+ <% sum_statement = 0.0 %>
+ %for statementline in statement.line_ids:
+
+ ## curency code
+
${statementline.name or '' }
+ ## currency balance
+
${statementline.partner_id.name or '' }
+ ## currency balance cumulated
+
${formatLang(statementline.amount) | amount }
+ <% sum_statement += statementline.amount %>
+
+ %endfor
+
+ ## curency code
+
+ ## currency balance
+
Total
+ ## currency balance cumulated
+
${formatLang(sum_statement) }
-
- <% sum_statement = 0.0 %>
- %for statementline in statement.line_ids:
-
- ## curency code
-
${statementline.name or '' }
- ## currency balance
-
${statementline.partner_id.name or '' }
- ## currency balance cumulated
-
${formatLang(statementline.amount) | amount }
- <% sum_statement += statementline.amount %>
-
- %endfor
-
- ## curency code
-
- ## currency balance
-
Total
- ## currency balance cumulated
-
${formatLang(sum_statement) }
-
-
-
- %endfor
+
+ %endfor
+
diff --git a/account_statement_ext/report/bank_statement_report.py b/account_statement_ext/report/bank_statement_report.py
index a06c1d9a..19ba2efe 100644
--- a/account_statement_ext/report/bank_statement_report.py
+++ b/account_statement_ext/report/bank_statement_report.py
@@ -27,8 +27,6 @@ import pooler
from operator import add, itemgetter
from itertools import groupby
from datetime import datetime
-
-#from common_report_header_webkit import CommonReportHeaderWebkit
from report_webkit import webkit_report
class BankStatementWebkit(report_sxw.rml_parse):
@@ -66,7 +64,7 @@ class BankStatementWebkit(report_sxw.rml_parse):
statement_lines = statement_obj.browse(self.cr,self.uid,statement_line_ids)
return statement_lines
-webkit_report.WebKitParser('report.report_bank_statement_webkit',
+webkit_report.WebKitParser('report.bank_statement_webkit',
'account.bank.statement',
- 'addons/account_statement_import/report/bank_statement_report.mako',
+ 'addons/account_statement_ext/report/bank_statement_report.mako',
parser=BankStatementWebkit)
diff --git a/account_statement_ext/statement.py b/account_statement_ext/statement.py
index 5fba0b19..276ce259 100644
--- a/account_statement_ext/statement.py
+++ b/account_statement_ext/statement.py
@@ -20,22 +20,28 @@
##############################################################################
from tools.translate import _
-from account_statement_ext.file_parser.parser import FileParser
import datetime
import netsvc
logger = netsvc.Logger()
from openerp.osv.orm import Model, fields
+from openerp.osv import fields, osv
class AccountStatementProfil(Model):
+ """
+ A Profile will contain all infos related to the type of
+ bank statement, and related generated entries. It defines the
+ journal to use, the partner and commision account and so on.
+ """
_name = "account.statement.profil"
_description = "Statement Profil"
_columns = {
'name': fields.char('Name', size=128, required=True),
'partner_id': fields.many2one('res.partner',
- 'Credit insitute 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 tic the corresponding checkbox)."),
+ '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 tic the corresponding checkbox)."),
'journal_id': fields.many2one('account.journal',
'Financial journal to use for transaction',
required=True),
@@ -54,13 +60,16 @@ class AccountStatementProfil(Model):
in the counterpart of the intermediat/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."
+ 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."
),
+ 'bank_statement_prefix': fields.char('Bank Statement Prefix', size=32),
+ 'bank_statement_ids': fields.one2many('account.bank.statement', 'profile_id', 'Bank Statement Imported'),
+
+
}
-
- _defaults = {}
-
+
def _check_partner(self, cr, uid, ids, context=None):
obj = self.browse(cr, uid, ids[0], context=context)
if obj.partner_id == False and obj.force_partner_on_bank:
@@ -73,43 +82,42 @@ class AccountStatementProfil(Model):
class AccountBankSatement(Model):
- """A kind of bank statement for intermediate move between customer and real bank, used
- for manageing check, payment office like paypal or marketplace like amazon.
- We inherit account.bank.statement because it's a very close object with only some
- difference. But we want some method to be completely different, so we create a new object."""
+ """
+ We improve the bank statement class mostly for :
+ - Removing the period and compute it from the date of each line.
+ - Allow to remove the balance check depending on the chosen profil
+ - Report errors on confirmation all at once instead of crashing onr by one
+ - Add a profil notion that can change the generated entries on statement
+ confirmation.
+ For this, we had to override quite some long method and we'll need to maintain
+ them up to date. Changes are point up by '#Chg' comment.
+ """
_inherit = "account.bank.statement"
_columns = {
- 'import_config_id': fields.many2one('account.statement.profil',
+ 'profile_id': fields.many2one('account.statement.profil',
'Profil', required=True, states={'draft': [('readonly', False)]}),
'credit_partner_id': fields.related(
- 'import_config_id',
+ 'profile_id',
'partner_id',
type='many2one',
relation='res.partner',
string='Financial Partner',
store=True, readonly=True),
'balance_check': fields.related(
- 'import_config_id',
+ 'profile_id',
'balance_check',
type='boolean',
string='Balance check',
store=True, readonly=True),
'journal_id': fields.related(
- 'import_config_id',
+ 'profile_id',
'journal_id',
type='many2one',
relation='account.journal',
string='Journal',
store=True, readonly=True),
- # 'line_ids': fields.one2many('account.bank.statement.line',
- # 'statement_id', 'Statement lines',
- # states={'confirm':[('readonly', True)]}),
- # 'move_line_ids': fields.one2many('account.move.line', 'statement_treasury_id',
- # 'Entry lines', states={'confirm':[('readonly',True)]}),
- # Redefine this field to avoid his computation (it is a function field on bank statement)
- # 'balance_end': fields.dummy(string="Computed Balance"),
'period_id': fields.many2one('account.period', 'Period', required=False, readonly=True),
}
@@ -119,17 +127,17 @@ class AccountBankSatement(Model):
def create(self, cr, uid, vals, context=None):
"""Need to pass the journal_id in vals anytime because of account.cash.statement
- that need it."""
- if 'import_config_id' in vals:
+ need it."""
+ if 'profile_id' in vals:
profil_obj = self.pool.get('account.statement.profil')
- profile = profil_obj.browse(cr,uid,vals['import_config_id'],context)
+ profile = profil_obj.browse(cr,uid,vals['profile_id'],context)
vals['journal_id'] = profile.journal_id.id
return super(AccountBankSatement, self).create(cr, uid, vals, context=context)
def _get_period(self, cursor, uid, date, context=None):
- '''
- Find matching period for date, used in thestatement line creation.
- '''
+ """
+ Find matching period for date, used in the statement line creation.
+ """
period_obj = self.pool.get('account.period')
periods = period_obj.find(cursor, uid, dt=date, context=context)
return periods and periods[0] or False
@@ -149,24 +157,46 @@ class AccountBankSatement(Model):
return False
return True
- # Redefine the constraint, or it still refer to the original method
_constraints = [
(_check_company_id, 'The journal and period chosen have to belong to the same company.', ['journal_id','period_id']),
]
+ def button_cancel(self, cr, uid, ids, context={}):
+ """
+ We cancel the related move, delete them and finally put the
+ statement in draft state. So no need to unreconcile all entries,
+ then unpost them, then finaly cancel the bank statement.
+ """
+ done = []
+ for st in self.browse(cr, uid, ids, context=context):
+ if st.state=='draft':
+ continue
+ ids = []
+ for line in st.line_ids:
+ for move in line.move_ids:
+ move.button_cancel(context=context)
+ move.unlink(context=context)
+ done.append(st.id)
+ self.write(cr, uid, done, {'state':'draft'}, context=context)
+ return True
+
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, st_line_number, context=None):
- """Override a large portion of the code to compute the periode for each line instead of
+ """
+ Override a large portion of the code to compute the periode for each line instead of
taking the period of the whole statement.
Remove the entry posting on generated account moves.
- Point to account.bank.statement.line instead of account.bank.statement.line.
- In Treasury Statement, unlike the Bank statement, we will change the move line generated from the
- lines depending on the profil (config import):
+ We change the move line generated from the lines depending on the profil:
- If receivable_account_id is set, we'll use it instead of the "partner" one
- If partner_id is set, we'll us it for the commission (when imported throufh the wizard)
- If partner_id is set and force_partner_on_bank is ticked, we'll let the partner of each line
for the debit line, but we'll change it on the credit move line for the choosen partner_id
- => This will ease the reconsiliation process with the bank as the partner will match the bank
+ => This will ease the reconciliation process with the bank as the partner will match the bank
statement line
+
+ :param int/long: st_line_id: account.bank.statement.line ID
+ :param int/long: company_currency_id: res.currency ID
+ :param char: st_line_number: that will be used as the name of the generated account move
+ :return: int/long: ID of the created account.move
"""
if context is None:
context = {}
@@ -246,8 +276,8 @@ class AccountBankSatement(Model):
amount_currency = st_line.amount
currency_id = st.currency.id
# GET THE RIGHT PARTNER ACCORDING TO THE CHOSEN PROFIL # Chg
- if st.import_config_id.force_partner_on_bank: # Chg
- bank_parrtner_id = st.import_config_id.partner_id.id # Chg
+ if st.profile_id.force_partner_on_bank: # Chg
+ bank_parrtner_id = st.profile_id.partner_id.id # Chg
else: # Chg
bank_parrtner_id = ((st_line.partner_id) and st_line.partner_id.id) or False # Chg
@@ -280,24 +310,40 @@ class AccountBankSatement(Model):
account_move_obj.post(cr, uid, [move_id], context=context)
return move_id
- def _get_st_number_period(self, cr, uid, date, journal_sequence_id):
- """Retrieve the name of bank statement from sequence, according to the period
- corresponding to the date passed in args"""
+ def _get_st_number_period_profil(self, cr, uid, date, profile_id):
+ """
+ Retrieve the name of bank statement from sequence, according to the period
+ corresponding to the date passed in args. Add a prefix if set in the profil.
+
+ :param: date: date of the statement used to compute the right period
+ :param: int/long: profile_id: the account.statement.profil ID from which to take the
+ bank_statement_prefix for the name
+ :return: char: name of the bank statement (st_number)
+
+ """
year = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, date)).fiscalyear_id.id
+ profile = self.pool.get('account.statement.profil').browse(cr,uid, profile_id)
c = {'fiscalyear_id': year}
obj_seq = self.pool.get('ir.sequence')
+ journal_sequence_id = profile.journal_id.sequence_id and profile.journal_id.sequence_id.id or False
if journal_sequence_id:
st_number = obj_seq.next_by_id(cr, uid, journal_sequence_id, context=c)
else:
st_number = obj_seq.next_by_code(cr, uid, 'account.bank.statement', context=c)
+ if profile.bank_statement_prefix:
+ st_number = profile.bank_statement_prefix + st_number
return st_number
def button_confirm_bank(self, cr, uid, ids, context=None):
- """Completely override the method in order to have
- an error message which displays all the messages
- instead of having them pop one by one.
- We have to copy paste a big block of code, changing the error
- stack + managing period from date."""
+ """
+ Completely override the method in order to have
+ an error message which displays all the messages
+ instead of having them pop one by one.
+ We have to copy paste a big block of code, changing the error
+ stack + managing period from date.
+
+ TODO: Log the error in a bank statement field instead of using a popup !
+ """
# obj_seq = self.pool.get('irerrors_stack.sequence')
if context is None:
context = {}
@@ -318,13 +364,7 @@ class AccountBankSatement(Model):
st_number = st.name
else:
# Begin Changes
- seq_id = st.journal_id.sequence_id and st.journal_id.sequence_id.id or False
- st_number = self._get_st_number_period(cr, uid, st.date, seq_id)
- # c = {'fiscalyear_id': st.period_id.fiscalyear_id.id}
- # if st.journal_id.sequence_id:
- # st_number = obj_seq.next_by_id(cr, uid, st.journal_id.sequence_id.id, context=c)
- # else:
- # st_number = obj_seq.next_by_code(cr, uid, 'account.bank.statement', context=c)
+ st_number = self._get_st_number_period_profil(cr, uid, st.date, st.profile_id.id)
# End Changes
for line in st.move_line_ids:
if line.state <> 'valid':
@@ -359,60 +399,19 @@ class AccountBankSatement(Model):
self.log(cr, uid, st.id, _('Statement %s is confirmed, journal items are created.') % (st_number,))
return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
- def get_partner_from_so(self, cursor, uid,transaction_id):
- """Look for the SO that has the given transaction_id, if not
- found, try to match the SO name instead. If still nothing,
- return False"""
- so_obj = self.pool.get('sale.order')
- so_id = so_obj.search(cursor, uid, [('transaction_id', '=', transaction_id)])
- if so_id and len(so_id) == 1:
- return so_obj.browse(cursor, uid, so_id[0]).partner_id.id
- else:
- so_id2 = so_obj.search(cursor, uid, [('name', '=', transaction_id)])
- if so_id2 and len(so_id2) == 1:
- return so_obj.browse(cursor, uid, so_id2[0]).partner_id.id
- return False
-
-
- def get_default_accounts(self, cursor, uid, receivable_account_id, context=None):
- """We try to determine default accounts if not receivable_account_id set, otherwise
- take it for both receivable and payable account"""
- account_receivable = False
- account_payable = False
- if receivable_account_id:
- account_receivable = account_payable = receivable_account_id
- else:
- context = context or {}
- property_obj = self.pool.get('ir.property')
- model_fields_obj = self.pool.get('ir.model.fields')
- model_fields_ids = model_fields_obj.search(
- cursor,
- uid,
- [('name', 'in', ['property_account_receivable',
- 'property_account_payable']),
- ('model', '=', 'res.partner'),],
- context=context
- )
- property_ids = property_obj.search(
- cursor,
- uid, [
- ('fields_id', 'in', model_fields_ids),
- ('res_id', '=', False),
- ],
- context=context
- )
-
- for erp_property in property_obj.browse(cursor, uid,
- property_ids, context=context):
- if erp_property.fields_id.name == 'property_account_receivable':
- account_receivable = erp_property.value_reference.id
- elif erp_property.fields_id.name == 'property_account_payable':
- account_payable = erp_property.value_reference.id
- return account_receivable, account_payable
-
- def _get_account_id(self, cursor, uid,
+ def get_account_for_counterpart(self, cursor, uid,
amount, account_receivable, account_payable):
- "return the default account to be used by statement line"
+ """
+ Give the amount, payable and receivable account (that can be found using
+ get_default_pay_receiv_accounts method) and receive the one to use. This method
+ should be use when there is no other way to know which one to take.
+
+ :param float: amount of the line
+ :param int/long: account_receivable the receivable account
+ :param int/long: account_payable the payable account
+ :return: int/long :the default account to be used by statement line as the counterpart
+ of the journal account depending on the amount.
+ """
account_id = False
if amount >= 0:
account_id = account_receivable
@@ -425,33 +424,71 @@ class AccountBankSatement(Model):
)
return account_id
+ def get_default_pay_receiv_accounts(self, cursor, uid, context=None):
+ """
+ We try to determine default payable/receivable accounts to be used as counterpart
+ from the company default propoerty. This is to be used if there is no otherway to
+ find the good one, or to find a default value that will be overriden by a completion
+ method (rules of account_statement_base_completion) afterwards.
+
+ :return: tuple of int/long ID that give account_receivable, account_payable based on
+ company default.
+ """
+ account_receivable = False
+ account_payable = False
+ context = context or {}
+ property_obj = self.pool.get('ir.property')
+ model_fields_obj = self.pool.get('ir.model.fields')
+ model_fields_ids = model_fields_obj.search(
+ cursor,
+ uid,
+ [('name', 'in', ['property_account_receivable',
+ 'property_account_payable']),
+ ('model', '=', 'res.partner'),],
+ context=context
+ )
+ property_ids = property_obj.search(
+ cursor,
+ uid, [
+ ('fields_id', 'in', model_fields_ids),
+ ('res_id', '=', False),
+ ],
+ context=context
+ )
+
+ for erp_property in property_obj.browse(cursor, uid,
+ property_ids, context=context):
+ if erp_property.fields_id.name == 'property_account_receivable':
+ account_receivable = erp_property.value_reference.id
+ elif erp_property.fields_id.name == 'property_account_payable':
+ account_payable = erp_property.value_reference.id
+ return account_receivable, account_payable
+
def balance_check(self, cr, uid, st_id, journal_type='bank', context=None):
- """Balance check depends on the profil. If no check for this profil is required,
- return True"""
+ """
+ Balance check depends on the profil. If no check for this profil is required,
+ return True and do nothing, otherwise call super.
+
+ :param int/long st_id: ID of the concerned account.bank.statement
+ :param char: journal_type that concern the bank statement
+ :return: True
+ """
st = self.browse(cr, uid, st_id, context=context)
if st.balance_check:
return super(AccountBankSatement,self).balance_check(cr, uid, st_id, journal_type, context)
else:
return True
-
- def _get_value_from_import_config(self, cr, uid, import_config_id):
- """Return a dict with with values taken from the given config.
- e.g. (journal_id, partner_id, commission_account_id, mode, forced_account_id)
- """
- # Get variable from config
- import_config = self.pool.get("account.statement.profil").browse(cr,uid,import_config_id)
- forced_account_id = import_config.receivable_account_id and import_config.receivable_account_id.id or False
- journal_id = import_config.journal_id and import_config.journal_id.id or False
- partner_id = import_config.partner_id and import_config.partner_id.id or False
- commission_account_id = import_config.commission_account_id.id
- commission_analytic_id = import_config.commission_analytic_id and import_config.commission_analytic_id.id or False
- force_partner_on_bank = import_config.force_partner_on_bank
- return journal_id, partner_id, commission_account_id, commission_analytic_id, forced_account_id, force_partner_on_bank
- def onchange_imp_config_id(self, cr, uid, ids, import_config_id, context=None):
- if not import_config_id:
+ def onchange_imp_config_id(self, cr, uid, ids, profile_id, context=None):
+ """
+ Compute values on the change of the profile.
+
+ :param: int/long: profile_id that changed
+ :return dict of dict with key = name of the field
+ """
+ if not profile_id:
return {}
- import_config = self.pool.get("account.statement.profil").browse(cr,uid,import_config_id)
+ import_config = self.pool.get("account.statement.profil").browse(cr,uid,profile_id)
journal_id = import_config.journal_id.id
account_id = import_config.journal_id.default_debit_account_id.id
credit_partner_id = import_config.partner_id and import_config.partner_id.id or False
@@ -460,166 +497,102 @@ class AccountBankSatement(Model):
'credit_partner_id':credit_partner_id,
}}
- def credit_statement_import(self, cursor, uid, ids,
- import_config_id,
- file_stream,
- ftype="csv",
- context=None):
- "Create statement from file stream encoded in base 64"
- context = context or {}
- statement_obj = self.pool.get('account.bank.statement')
- statement_line_obj = self.pool.get('account.bank.statement.line')
- attachment_obj = self.pool.get('ir.attachment')
-
- # Get variable from config
- journal_id, partner_id, commission_account_id, commission_analytic_id, \
- forced_account_id, force_partner_on_bank = self._get_value_from_import_config(cursor,uid,import_config_id)
-
- account_receivable, account_payable = self.get_default_accounts(cursor, uid, forced_account_id)
-
- ##Order of cols does not matter but first row has to be header
- keys = ['transaction_id', 'label', 'date', 'amount', 'commission_amount']
- #required_values = ['transaction_id', 'amount', 'commission_amount']
- convertion_dict = {
- 'transaction_id': unicode,
- 'label': unicode,
- 'date': datetime.datetime,
- 'amount': float,
- 'commission_amount': float
- }
-
- f_parser = FileParser(file_stream,
- keys_to_validate=keys,
- decode_base_64=True,
- ftype=ftype)
- statement_lines = f_parser.parse()
- statement_lines = f_parser.cast_rows(statement_lines, convertion_dict)
- journal = self.pool.get('account.journal').browse(cursor, uid, journal_id)
- statement_id = statement_obj.create(cursor,
- uid,
- { 'import_config_id':import_config_id,
- 'journal_id': journal_id,
- 'journal_id': journal_id,
- 'credit_partner_id': partner_id,
- 'statement_type': 'credit_partner',
- },
- context)
- commission_global_amount = 0.0
- if not journal.default_debit_account_id \
- or not journal.default_credit_account_id:
- raise osv.except_osv(
- _("Missing default account on journal %s")%(journal.name),
- _("Please correct the journal"))
- try:
- for line in statement_lines:
- line_partner_id = False
- line_to_reconcile = False
- # We ensure that required values of the line are set
-# for val in required_values:
-# if not line.get(val, False) and line.get(val, False) != 0.0:
-# raise osv.except_osv(
-# _("Field %s not set for line %s")%(str(line),),
-# _("Please correct the file"))
-
- commission_global_amount += line.get('commission_amount', 0.0)
- values = {
- 'name': "IN %s %s"%(line['transaction_id'],
- line.get('label', '')),
- 'date': line.get('date', datetime.datetime.now().date()),
- 'amount': line['amount'],
- 'ref': "TID_%s"%(line['transaction_id'],),
- 'type': 'customer',
- 'statement_id': statement_id,
- #'account_id': journal.default_debit_account_id
- }
- values['account_id'] = self._get_account_id(
- cursor,
- uid,
- line['amount'],
- account_receivable,
- account_payable
- )
- if not line_partner_id:
- line_partner_id = self.get_partner_from_so(cursor,
- uid, line['transaction_id'])
- values['partner_id'] = line_partner_id
- # we finally create the line in system
- statement_line_obj.create(cursor, uid, values, context=context)
-
- # we create commission line
- if commission_global_amount:
- comm_values = {
- 'name': 'IN '+ _('Commission line'),
- 'date': datetime.datetime.now().date(),
- 'amount': commission_global_amount,
- 'partner_id': partner_id,
- 'type': 'general',
- 'statement_id': statement_id,
- 'account_id': commission_account_id,
- 'ref': 'commission',
- 'analytic_account_id': commission_analytic_id
- }
- statement_line_obj.create(cursor, uid,
- comm_values,
- context=context)
-
- attachment_obj.create(
- cursor,
- uid,
- {
- 'name': 'statement file',
- 'datas': file_stream,
- 'datas_fname': "%s.%s"%(datetime.datetime.now().date(),
- ftype),
- 'res_model': 'account.bank.statement',
- 'res_id': statement_id,
- },
- context=context
- )
- except Exception, exc:
- logger.notifyChannel("Statement import",
- netsvc.LOG_ERROR,
- _("Statement can not be created %s") %(exc,))
-
- statement_obj.unlink(cursor, uid, [statement_id])
- raise exc
- return statement_id
-
-
class AccountBankSatementLine(Model):
+ """
+ Override to compute the period from the date of the line, add a method to retrieve
+ the values for a line from the profile. Override the on_change method to take care of
+ the profile when fullfilling the bank statement manually. Set the reference to 64
+ Char long instead 32.
+ """
_inherit = "account.bank.statement.line"
def _get_period(self, cursor, user, context=None):
+ """
+ Return a period from a given date in the context.
+ """
date = context.get('date', None)
periods = self.pool.get('account.period').find(cursor, user, dt=date)
return periods and periods[0] or False
_columns = {
- # 'statement_id': fields.many2one('account.bank.statement', 'Statement',
- # select=True, required=True, ondelete='cascade'),
- # 'move_ids': fields.many2many('account.move',
- # 'account_treasury_statement_line_move_rel', 'statement_line_id','move_id',
- # 'Moves'),
- 'ref': fields.char('Reference', size=32, required=True),
+ # Set them as required + 64 char instead of 32
+ 'ref': fields.char('Reference', size=64, required=True),
'period_id': fields.many2one('account.period', 'Period', required=True),
}
_defaults = {
'period_id': _get_period,
}
-
- # WARNING => Crash cause the super method here calls onchange_type => and then
- # we don't call it from the good model.... => We'll need to override the complete method here
- def onchange_partner_id(self, cr, uid, ids, partner_id, import_config_id, context=None):
- # import pdb;pdb.set_trace()
- # if context is None:
- # context = {}
- # res = super(AccountTreasurySatementLine,self).onchange_partner_id(cr, uid, ids, partner_id, context)
- # c = self.pool.get("account.statement.profil").browse(cr,uid,import_config_id)
- # acc_id=c.receivable_account_id and c.receivable_account_id.id or False
- # if acc_id:
- # res['value'].update({'account_id':acc_id})
- # return res
+
+ def get_values_for_line(self, cr, uid, profile_id = False, partner_id = False, line_type = False, amount = False, context = None):
+ """
+ Return the account_id to be used in the line of a bank statement. It'll base the result as follow:
+ - If a receivable_account_id is set in the profil, return this value and type = general
+ - Elif line_type is given, take the partner receivable/payable property (payable if type= supplier, receivable
+ otherwise)
+ - Elif amount is given, take the partner receivable/payable property (receivable if amount >= 0.0,
+ payable otherwise). In that case, we also fullfill the type (receivable = customer, payable = supplier)
+ so it is easier for the accountant to know why the receivable/payable has been chosen
+ - Then, if no partner are given we look and take the property from the company so we always give a value
+ for account_id. Note that in that case, we return the receivable one.
+
+ :param int/long profile_id of the related bank statement
+ :param int/long partner_id of the line
+ :param char line_type: a value from: 'general', 'supplier', 'customer'
+ :param float: amount of the line
+ :return: A dict of value that can be passed directly to the write method of
+ the statement line:
+ {'partner_id': value,
+ 'account_id' : value,
+ 'type' : value,
+ ...
+ }
+ """
+ if context is None:
+ context = {}
+ res = {}
+ obj_partner = self.pool.get('res.partner')
+ obj_stat = self.pool.get('account.bank.statement')
+ receiv_account = pay_account = account_id = False
+ # If profil has a receivable_account_id, we return it in any case
+ if profile_id:
+ profile = self.pool.get("account.statement.profil").browse(cr,uid,profile_id)
+ if profile.receivable_account_id:
+ res['account_id'] = profile.receivable_account_id.id
+ res['type'] = 'general'
+ return res
+ # If partner -> take from him
+ if partner_id:
+ part = obj_partner.browse(cr, uid, partner_id, context=context)
+ pay_account = part.property_account_payable.id
+ receiv_account = part.property_account_receivable.id
+ # If no value, look on the default company property
+ if not pay_account or not receiv_account:
+ receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts(cr, uid, context=None)
+ # Now we have both pay and receive account, choose the one to use
+ # based on line_type first, then amount, otherwise take receivable one.
+ if line_type is not False:
+ if line_type == 'supplier':
+ res['account_id'] = pay_account
+ else:
+ res['account_id'] = receiv_account
+ elif amount is not False:
+ if amount >= 0:
+ res['account_id'] = receiv_account
+ res['type'] = 'customer'
+ else:
+ res['account_id'] = pay_account
+ res['type'] = 'supplier'
+ if not account_id:
+ res['account_id'] = receiv_account
+ return res
+
+
+ def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id, context=None):
+ """
+ Override of the basic method as we need to pass the profile_id in the on_change_type
+ call.
+ """
obj_partner = self.pool.get('res.partner')
if context is None:
context = {}
@@ -635,34 +608,23 @@ class AccountBankSatementLine(Model):
type = 'supplier'
if part.customer == True:
type = 'customer'
- res_type = self.onchange_type(cr, uid, ids, partner_id, type, import_config_id, context=context)
+ res_type = self.onchange_type(cr, uid, ids, partner_id, type, profile_id, context=context) # Chg
if res_type['value'] and res_type['value'].get('account_id', False):
- res = {'value': {'type': type, 'account_id': res_type['value']['account_id']}}
- else:
- res = {'value': {'type': type}}
-
- c = self.pool.get("account.statement.profil").browse(cr,uid,import_config_id)
- acc_id=c.receivable_account_id and c.receivable_account_id.id or False
- if acc_id:
- res['value'].update({'account_id':acc_id})
- return res
+ return {'value': {'type': type, 'account_id': res_type['value']['account_id']}}
+ return {'value': {'type': type}}
- # TOFIX
- def onchange_type(self, cr, uid, line_id, partner_id, type, import_config_id, context=None):
+ def onchange_type(self, cr, uid, line_id, partner_id, type, profile_id, context=None):
+ """
+ Keep the same features as in standard and call super. If an account is returned,
+ call the method to compute line values.
+ """
if context is None:
context = {}
res = super(AccountBankSatementLine,self).onchange_type(cr, uid, line_id, partner_id, type, context)
- c = self.pool.get("account.statement.profil").browse(cr,uid,import_config_id)
- acc_id=c.receivable_account_id and c.receivable_account_id.id or False
- if acc_id:
- res['value'].update({'account_id':acc_id})
+ if 'account_id' in res['value']:
+ result = self.get_values_for_line(cr, uid, profile_id = profile_id,
+ partner_id = partner_id, line_type = type, context = context)
+ if result:
+ res['value'].update({'account_id':result['account_id']})
return res
-# class AccountMoveLine(Model):
-# _inherit = "account.move.line"
-#
-# _columns = {
-# 'statement_treasury_id': fields.many2one('account.bank.statement', 'Statement', help="The intermediate statement used for reconciliation", select=1),
-# }
-
-
diff --git a/account_statement_ext/statement_view.xml b/account_statement_ext/statement_view.xml
index 0ea59c0a..6f8761a9 100644
--- a/account_statement_ext/statement_view.xml
+++ b/account_statement_ext/statement_view.xml
@@ -40,6 +40,7 @@
+
@@ -90,7 +91,7 @@
search
-
+
@@ -109,7 +110,7 @@
tree
-
+
@@ -133,7 +134,7 @@
-
+
@@ -166,53 +167,36 @@
-
+
-
+
-
+
-
+
-
+
+
-
diff --git a/account_statement_transactionid_completion/__init__.py b/account_statement_transactionid_completion/__init__.py
new file mode 100644
index 00000000..a8ce7c24
--- /dev/null
+++ b/account_statement_transactionid_completion/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Joel Grand-Guillaume
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import statement
diff --git a/account_statement_transactionid_completion/__openerp__.py b/account_statement_transactionid_completion/__openerp__.py
new file mode 100644
index 00000000..caddd6bf
--- /dev/null
+++ b/account_statement_transactionid_completion/__openerp__.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Joel Grand-Guillaume
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+{'name': "Bank statement completion from transaction ID",
+ 'version': '1.0',
+ 'author': 'Camptocamp',
+ 'maintainer': 'Camptocamp',
+ 'category': 'Finance',
+ 'complexity': 'normal', #easy, normal, expert
+ 'depends': ['account_statement_base_completion', 'base_transaction_id'],
+ 'description': """
+ Add a completion method based on transaction ID providen by the bank/office. This
+ transaction ID has been recorded on the SO (by a mapping through the e-commerce connector,
+ or manually). Completion will look in the SO with that transaction ID to match the partner,
+ then it will complete the bank statement line with him the fullfill as well the reference
+ with the found SO name to ease the reconciliation.
+
+ So this way, the reconciliation always happend on the SO name stored in ref.
+
+ """,
+ 'website': 'http://www.camptocamp.com',
+ 'init_xml': [],
+ 'update_xml': [
+ "statement_view.xml",
+ "data.xml",
+ ],
+ 'demo_xml': [],
+ 'test': [],
+ 'installable': True,
+ 'images': [],
+ 'auto_install': True,
+ 'license': 'AGPL-3',
+ 'active': False,
+}
diff --git a/account_statement_transactionid_completion/data.xml b/account_statement_transactionid_completion/data.xml
new file mode 100644
index 00000000..e44e9fe6
--- /dev/null
+++ b/account_statement_transactionid_completion/data.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Match from line reference (based on transaction ID)
+ 40
+ get_from_transaction_id_and_so
+
+
+
+
diff --git a/account_statement_transactionid_completion/statement.py b/account_statement_transactionid_completion/statement.py
new file mode 100644
index 00000000..036bf65b
--- /dev/null
+++ b/account_statement_transactionid_completion/statement.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Joel Grand-Guillaume
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from tools.translate import _
+import datetime
+import netsvc
+logger = netsvc.Logger()
+from openerp.osv.orm import Model, fields
+from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner
+
+class AccountStatementCompletionRule(Model):
+ """Add a rule based on transaction ID"""
+
+ _inherit = "account.statement.completion.rule"
+
+ def _get_functions(self, cr, uid, context=None):
+ res = super (AccountStatementCompletionRule, self)._get_functions(
+ cr, uid, context=context)
+ res.append(('get_from_transaction_id_and_so', 'From line reference (based on SO transaction ID'))
+ return res
+
+ _columns={
+ 'function_to_call': fields.selection(_get_functions, 'Method'),
+ }
+
+ def get_from_transaction_id_and_so(self, cr, uid, line_id, context=None):
+ """
+ Match the partner based on the transaction ID field of the SO.
+ Then, call the generic st_line method to complete other values.
+ In that case, we always fullfill the reference of the line with the SO name.
+ :param int/long line_id: ID 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,
+ ...}
+ """
+ st_obj = self.pool.get('account.bank.statement.line')
+ st_line = st_obj.browse(cr,uid,line_id)
+ res = {}
+ if st_line:
+ so_obj = self.pool.get('sale.order')
+ so_id = so_obj.search(cursor, uid, [('transaction_id', '=', st_line.transaction_id)])
+ if so_id and len(so_id) == 1:
+ so = so_obj.browse(cursor, uid, so_id[0])
+ res['partner_id'] = so.partner_id.id
+ res['ref'] = so.name
+ elif so_id and len(so_id) > 1:
+ raise ErrorTooManyPartner(_('Line named "%s" was matched by more than one partner.')%(st_line.name,st_line.id))
+ if so_id:
+ st_vals = st_obj.get_values_for_line(cr, uid, profile_id = st_line.statement_id.profile_id.id,
+ partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context=context)
+ res.update(st_vals)
+ return res
+
+
+class AccountStatementLine(Model):
+ _inherit = "account.bank.statement.line"
+
+ _columns={
+ # 'additionnal_bank_fields' : fields.serialized('Additionnal infos from bank', help="Used by completion and import system."),
+ 'transaction_id': fields.sparse(type='char', string='Transaction ID',
+ size=128,
+ serialization_field='additionnal_bank_fields',
+ help="Transction id from the financial institute"),
+ }
+
+
+
+
+
diff --git a/account_statement_transactionid_completion/statement_view.xml b/account_statement_transactionid_completion/statement_view.xml
new file mode 100644
index 00000000..f7612d17
--- /dev/null
+++ b/account_statement_transactionid_completion/statement_view.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ account_bank_statement_import_base.bank_statement.view_form
+ account.bank.statement
+
+
+ form
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/account_statement_ext/file_parser/__init__.py b/account_statement_transactionid_import/__init__.py
similarity index 96%
rename from account_statement_ext/file_parser/__init__.py
rename to account_statement_transactionid_import/__init__.py
index b0665b1c..53424340 100644
--- a/account_statement_ext/file_parser/__init__.py
+++ b/account_statement_transactionid_import/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Author: Nicolas Bessi
+# Author: Joel Grand-Guillaume
# Copyright 2011-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
@@ -18,5 +18,4 @@
# along with this program. If not, see .
#
##############################################################################
-
import parser
\ No newline at end of file
diff --git a/account_statement_transactionid_import/__openerp__.py b/account_statement_transactionid_import/__openerp__.py
new file mode 100644
index 00000000..20d7f082
--- /dev/null
+++ b/account_statement_transactionid_import/__openerp__.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Joel Grand-Guillaume
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+{'name': "Bank statement transactionID import",
+ 'version': '1.0',
+ 'author': 'Camptocamp',
+ 'maintainer': 'Camptocamp',
+ 'category': 'Finance',
+ 'complexity': 'normal', #easy, normal, expert
+ 'depends': ['account_statement_base_import','account_statement_transactionid_completion'],
+ 'description': """
+ This module brings generic methods and fields on bank statement to deal with
+ the importation of different bank and offices that uses transactionID.
+
+ This module allow you to import your bank transactions with a standard .csv or .xls file
+ (you'll find it in the 'datas' folder). It'll respect the profil
+ you'll choose (providen by the account_statement_ext module) to generate the entries.
+
+ This module can handle a commission taken by the payment office and has the following format:
+
+ * transaction_id : the transaction ID given by the bank/office. It'll be used as reference
+ in the generated entries and will be useful for reconciliation process
+ * date : date of the payment
+ * amount : amount paid in the currency of the journal used in the importation profil
+ * commission_amount : amount of the comission for each line
+ * label : the comunication given by the payment office, used as communication in the
+ generated entries.
+
+
+ """,
+ 'website': 'http://www.camptocamp.com',
+ 'init_xml': [],
+ 'update_xml': [
+ ],
+ 'demo_xml': [],
+ 'test': [],
+ 'installable': True,
+ 'images': [],
+ 'auto_install': False,
+ 'license': 'AGPL-3',
+ 'active': False,
+}
diff --git a/account_statement_ext/datas/statement.csv b/account_statement_transactionid_import/datas/statement.csv
similarity index 100%
rename from account_statement_ext/datas/statement.csv
rename to account_statement_transactionid_import/datas/statement.csv
diff --git a/account_statement_ext/datas/statement.xls b/account_statement_transactionid_import/datas/statement.xls
similarity index 90%
rename from account_statement_ext/datas/statement.xls
rename to account_statement_transactionid_import/datas/statement.xls
index 13eeafee..07cc90d7 100644
Binary files a/account_statement_ext/datas/statement.xls and b/account_statement_transactionid_import/datas/statement.xls differ
diff --git a/account_statement_transactionid_import/parser/__init__.py b/account_statement_transactionid_import/parser/__init__.py
new file mode 100644
index 00000000..4fd2e664
--- /dev/null
+++ b/account_statement_transactionid_import/parser/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Nicolas Bessi
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+# from parser import new_bank_statement_parser
+# from parser import BankStatementImportParser
+import transactionid_file_parser
\ No newline at end of file
diff --git a/account_statement_transactionid_import/parser/transactionid_file_parser.py b/account_statement_transactionid_import/parser/transactionid_file_parser.py
new file mode 100644
index 00000000..cae60d40
--- /dev/null
+++ b/account_statement_transactionid_import/parser/transactionid_file_parser.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright Camptocamp SA
+# Author Joel Grand-Guillaume
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.tools.translate import _
+import base64
+import csv
+import tempfile
+import datetime
+from account_statement_base_import.parser.file_parser import FileParser
+
+try:
+ import xlrd
+except:
+ raise Exception(_('Please install python lib xlrd'))
+
+class TransactionIDFileParser(FileParser):
+ """
+ TransactionID parser that use a define format in csv or xls to import
+ bank statement.
+ """
+
+ def __init__(self, parse_name, ftype='csv'):
+ convertion_dict = {
+ 'transaction_id': unicode,
+ 'label': unicode,
+ 'date': datetime.datetime,
+ 'amount': float,
+ 'commission_amount': float
+ }
+ # Order of cols does not matter but first row of the file has to be header
+ keys_to_validate = ['transaction_id', 'label', 'date', 'amount', 'commission_amount']
+ super(TransactionIDFileParser,self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, convertion_dict=convertion_dict)
+
+ @classmethod
+ def parser_for(cls, parser_name):
+ """
+ Used by the new_bank_statement_parser class factory. Return true if
+ the providen name is generic_csvxls_transaction
+ """
+ return parser_name == 'generic_csvxls_transaction'
+
+ def get_st_line_vals(self, line, *args, **kwargs):
+ """
+ This method must return a dict of vals that can be passed to create
+ method of statement line in order to record it. It is the responsibility
+ of every parser to give this dict of vals, so each one can implement his
+ own way of recording the lines.
+ :param: line: a dict of vals that represent a line of result_row_list
+ :return: dict of values to give to the create method of statement line,
+ it MUST contain at least:
+ {
+ 'name':value,
+ 'date':value,
+ 'amount':value,
+ 'ref':value,
+ 'label':value,
+ 'commission_amount':value,
+ }
+ In this generic parser, the commission is given for every line, so we store it
+ for each one.
+ """
+ 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('transaction_id','/'),
+ 'label': line.get('label',''),
+ 'transaction_id': line.get('transaction_id','/'),
+ 'commission_amount': line.get('commission_amount', 0.0),
+ }
+
+ def _post(self, *args, **kwargs):
+ """
+ Compute the commission from value of each line
+ """
+ res = super(GenericFileParser, self)._post(*args, **kwargs)
+ val = 0.0
+ for row in self.result_row_list:
+ val += row.get('commission_amount',0.0)
+ self.commission_global_amount = val
+ return res
+