diff --git a/account_banking/__init__.py b/account_banking/__init__.py index 1d7fc15bc..d7a19ee70 100644 --- a/account_banking/__init__.py +++ b/account_banking/__init__.py @@ -11,8 +11,8 @@ # garantees and support are strongly adviced to contract EduSense BV # # 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 +# 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, @@ -24,6 +24,7 @@ # along with this program. If not, see . # ############################################################################## + import sepa import record import banking_import_transaction @@ -33,5 +34,3 @@ import wizard import res_partner import res_bank import res_partner_bank - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/__openerp__.py b/account_banking/__openerp__.py index bb2385407..b03ad1703 100644 --- a/account_banking/__openerp__.py +++ b/account_banking/__openerp__.py @@ -70,7 +70,8 @@ + IBAN accounts are the standard in the SEPA countries + local accounts are derived from SEPA (excluding Turkey) but are considered to be identical to the corresponding SEPA account. - + Banks are identified with either Country + Bank code + Branch code or BIC + + Banks are identified with either Country + Bank code + Branch code or + BIC + Each bank can have its own pace in introducing SEPA into their communication with their customers. + National online databases can be used to convert BBAN's to IBAN's. diff --git a/account_banking/account_banking.py b/account_banking/account_banking.py index 971df55ee..9aec2d8f8 100644 --- a/account_banking/account_banking.py +++ b/account_banking/account_banking.py @@ -3,7 +3,7 @@ # # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2013 Therp BV (). -# +# # All other contributions are (C) by their respective contributors # # All Rights Reserved @@ -86,11 +86,11 @@ class account_banking_account_settings(orm.Model): string='Partner'), 'default_credit_account_id': fields.many2one( 'account.account', 'Default credit account', select=True, - help=('The account to use when an unexpected payment was signaled. ' - 'This can happen when a direct debit payment is cancelled ' + help=('The account to use when an unexpected payment was signaled.' + ' This can happen when a direct debit payment is cancelled ' 'by a customer, or when no matching payment can be found. ' - ' Mind that you can correct movements before confirming them.' - ), + 'Mind that you can correct movements before confirming them.' + ), required=True ), 'default_debit_account_id': fields.many2one( @@ -98,27 +98,27 @@ class account_banking_account_settings(orm.Model): select=True, required=True, help=('The account to use when an unexpected payment is received. ' 'This can be needed when a customer pays in advance or when ' - 'no matching invoice can be found. Mind that you can correct ' - 'movements before confirming them.' - ), + 'no matching invoice can be found. Mind that you can ' + 'correct movements before confirming them.' + ), ), 'costs_account_id': fields.many2one( 'account.account', 'Bank Costs Account', select=True, help=('The account to use when the bank invoices its own costs. ' 'Leave it blank to disable automatic invoice generation ' 'on bank costs.' - ), + ), ), 'invoice_journal_id': fields.many2one( - 'account.journal', 'Costs Journal', + 'account.journal', 'Costs Journal', help=('This is the journal used to create invoices for bank costs.' - ), + ), ), 'bank_partner_id': fields.many2one( 'res.partner', 'Bank Partner', help=('The partner to use for bank costs. Banks are not partners ' 'by default. You will most likely have to create one.' - ), + ), ), } @@ -134,7 +134,7 @@ class account_banking_account_settings(orm.Model): return user['company_id'][0] return self.pool.get('res.company').search( cr, uid, [('parent_id', '=', False)])[0] - + def _default_partner_id(self, cr, uid, context=None, company_id=False): if not company_id: company_id = self._default_company(cr, uid, context=context) @@ -196,7 +196,7 @@ class account_banking_account_settings(orm.Model): values['journal_id'] = bank['journal_id'][0] return {'value': values} - def onchange_company_id ( + def onchange_company_id( self, cr, uid, ids, company_id=False, context=None): if not company_id: return {} @@ -229,37 +229,59 @@ class account_banking_imported_file(orm.Model): _description = __doc__ _rec_name = 'date' _columns = { - 'company_id': fields.many2one('res.company', 'Company', - select=True, readonly=True - ), - 'date': fields.datetime('Import Date', readonly=True, select=True, - states={'draft': [('readonly', False)]} - ), - 'format': fields.char('File Format', size=20, readonly=True, - states={'draft': [('readonly', False)]} - ), - 'file': fields.binary('Raw Data', readonly=True, - states={'draft': [('readonly', False)]} - ), - 'file_name': fields.char('File name', size=256), - 'log': fields.text('Import Log', readonly=True, - states={'draft': [('readonly', False)]} - ), - 'user_id': fields.many2one('res.users', 'Responsible User', - readonly=True, select=True, - states={'draft': [('readonly', False)]} - ), - 'state': fields.selection( - [('unfinished', 'Unfinished'), - ('error', 'Error'), - ('review', 'Review'), - ('ready', 'Finished'), - ], 'State', select=True, readonly=True + 'company_id': fields.many2one( + 'res.company', + 'Company', + select=True, + readonly=True, + ), + 'date': fields.datetime( + 'Import Date', + readonly=True, + select=True, + states={'draft': [('readonly', False)]}, + ), + 'format': fields.char( + 'File Format', + size=20, + readonly=True, + states={'draft': [('readonly', False)]}, + ), + 'file': fields.binary( + 'Raw Data', + readonly=True, + states={'draft': [('readonly', False)]}, + ), + 'file_name': fields.char('File name', size=256), + 'log': fields.text( + 'Import Log', + readonly=True, + states={'draft': [('readonly', False)]}, + ), + 'user_id': fields.many2one( + 'res.users', + 'Responsible User', + readonly=True, + select=True, + states={'draft': [('readonly', False)]}, + ), + 'state': fields.selection( + [ + ('unfinished', 'Unfinished'), + ('error', 'Error'), + ('review', 'Review'), + ('ready', 'Finished'), + ], + 'State', + select=True, + readonly=True, + ), + 'statement_ids': fields.one2many( + 'account.bank.statement', + 'banking_id', + 'Statements', + readonly=False, ), - 'statement_ids': fields.one2many('account.bank.statement', - 'banking_id', 'Statements', - readonly=False, - ), } _defaults = { 'date': fields.date.context_today, @@ -284,11 +306,17 @@ class account_bank_statement(orm.Model): _inherit = 'account.bank.statement' _columns = { - 'period_id': fields.many2one('account.period', 'Period', - required=False, readonly=True), - 'banking_id': fields.many2one('account.banking.imported.file', - 'Imported File', readonly=True, - ), + 'period_id': fields.many2one( + 'account.period', + 'Period', + required=False, + readonly=True, + ), + 'banking_id': fields.many2one( + 'account.banking.imported.file', + 'Imported File', + readonly=True, + ), } _defaults = { @@ -316,12 +344,12 @@ class account_bank_statement(orm.Model): statement.write({'period_id': line.period_id.id}) statement.refresh() 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']), + ['journal_id', 'period_id']), ] def _get_period(self, cr, uid, date=False, context=None): @@ -410,7 +438,7 @@ class account_bank_statement(orm.Model): account_move_obj.post( cr, uid, [st_line.voucher_id.move_id.id], context={}) else: - # Write stored reconcile_id and pay invoices through workflow + # Write stored reconcile_id and pay invoices through workflow if st_line.reconcile_id: move_ids = [move.id for move in st_line.move_ids] torec = account_move_line_obj.search( @@ -419,11 +447,12 @@ class account_bank_statement(orm.Model): ('account_id', '=', st_line.account_id.id)], context=context) account_move_line_obj.write(cr, uid, torec, { - (st_line.reconcile_id.line_partial_ids and - 'reconcile_partial_id' or 'reconcile_id'): - st_line.reconcile_id.id }, context=context) + (st_line.reconcile_id.line_partial_ids + and 'reconcile_partial_id' + or 'reconcile_id'): st_line.reconcile_id.id + }, context=context) for move_line in (st_line.reconcile_id.line_id or []) + ( - st_line.reconcile_id.line_partial_ids or []): + st_line.reconcile_id.line_partial_ids or []): netsvc.LocalService("workflow").trg_trigger( uid, 'account.move.line', move_line.id, cr) return res @@ -438,7 +467,7 @@ class account_bank_statement(orm.Model): if ids and isinstance(ids, (int, long)): ids = [ids] noname_ids = self.search( - cr, uid, [('id', 'in', ids),('name', '=', '/')], + cr, uid, [('id', 'in', ids), ('name', '=', '/')], context=context) for st in self.browse(cr, uid, noname_ids, context=context): if st.journal_id.sequence_id: @@ -451,7 +480,7 @@ class account_bank_statement(orm.Model): cr, uid, st.journal_id.sequence_id.id, context=c) self.write( cr, uid, ids, {'name': st_number}, context=context) - + return super(account_bank_statement, self).button_confirm_bank( cr, uid, ids, context) @@ -464,7 +493,7 @@ class account_voucher(orm.Model): context = {} if not context.get('period_id') and context.get('move_line_ids'): move_line = self.pool.get('account.move.line').browse( - cr, uid , context.get('move_line_ids')[0], context=context) + cr, uid, context.get('move_line_ids')[0], context=context) return move_line.period_id.id return super(account_voucher, self)._get_period(cr, uid, context) @@ -498,8 +527,8 @@ class account_bank_statement_line(orm.Model): which is inaccessible from within this method. ''' res_users_obj = self.pool.get('res.users') - return res_users_obj.browse(cr, uid, uid, - context=context).company_id.currency_id.id + return res_users_obj.browse( + cr, uid, uid, context=context).company_id.currency_id.id def _get_invoice_id(self, cr, uid, ids, name, args, context=None): res = {} @@ -509,7 +538,7 @@ class account_bank_statement_line(orm.Model): st_line.reconcile_id and (st_line.reconcile_id.line_id or st_line.reconcile_id.line_partial_ids) or - st_line.import_transaction_id and + st_line.import_transaction_id and st_line.import_transaction_id.move_line_id and [st_line.import_transaction_id.move_line_id] or []): if move_line.invoice: @@ -519,36 +548,70 @@ class account_bank_statement_line(orm.Model): _columns = { # Redefines. Todo: refactor away to view attrs - 'amount': fields.float('Amount', readonly=True, - digits_compute=dp.get_precision('Account'), - states={'draft': [('readonly', False)]}), - 'ref': fields.char('Ref.', size=32, readonly=True, - states={'draft': [('readonly', False)]}), - 'name': fields.char('Name', size=64, required=False, readonly=True, - states={'draft': [('readonly', False)]}), - 'date': fields.date('Date', required=True, readonly=True, - states={'draft': [('readonly', False)]}), - + 'amount': fields.float( + 'Amount', + readonly=True, + digits_compute=dp.get_precision('Account'), + states={'draft': [('readonly', False)]}, + ), + 'ref': fields.char( + 'Ref.', + size=32, + readonly=True, + states={'draft': [('readonly', False)]}, + ), + 'name': fields.char( + 'Name', + size=64, + required=False, + readonly=True, + states={'draft': [('readonly', False)]}, + ), + 'date': fields.date( + 'Date', + required=True, + readonly=True, + states={'draft': [('readonly', False)]}, + ), # New columns - 'trans': fields.char('Bank Transaction ID', size=15, required=False, - readonly=True, - states={'draft':[('readonly', False)]}, - ), - 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account', - required=False, readonly=True, - states={'draft':[('readonly', False)]}, - ), - 'period_id': fields.many2one('account.period', 'Period', required=True, - states={'confirmed': [('readonly', True)]}), - 'currency': fields.many2one('res.currency', 'Currency', required=True, - states={'confirmed': [('readonly', True)]}), + 'trans': fields.char( + 'Bank Transaction ID', + size=15, + required=False, + readonly=True, + states={'draft': [('readonly', False)]}, + ), + 'partner_bank_id': fields.many2one( + 'res.partner.bank', + 'Bank Account', + required=False, + readonly=True, + states={'draft': [('readonly', False)]}, + ), + 'period_id': fields.many2one( + 'account.period', + 'Period', + required=True, + states={'confirmed': [('readonly', True)]}, + ), + 'currency': fields.many2one( + 'res.currency', + 'Currency', + required=True, + states={'confirmed': [('readonly', True)]}, + ), 'reconcile_id': fields.many2one( - 'account.move.reconcile', 'Reconciliation', readonly=True - ), + 'account.move.reconcile', + 'Reconciliation', + readonly=True, + ), 'invoice_id': fields.function( - _get_invoice_id, method=True, string='Linked Invoice', - type='many2one', relation='account.invoice' - ), + _get_invoice_id, + method=True, + string='Linked Invoice', + type='many2one', + relation='account.invoice', + ), } _defaults = { @@ -577,9 +640,9 @@ class invoice(orm.Model): _inherit = 'account.invoice' def test_undo_paid(self, cr, uid, ids, context=None): - """ + """ Called from the workflow. Used to unset paid state on - invoices that were paid with bank transfers which are being cancelled + invoices that were paid with bank transfers which are being cancelled """ for invoice in self.read(cr, uid, ids, ['reconciled'], context): if invoice['reconciled']: @@ -592,22 +655,20 @@ class invoice(orm.Model): ''' return [('none', _('Free Reference')), ('structured', _('Structured Reference')), - ] + ] _columns = { 'reference_type': fields.selection(_get_reference_type, 'Reference Type', required=True - ) + ) } -invoice() - class account_move_line(orm.Model): _inherit = "account.move.line" def get_balance(self, cr, uid, ids, context=None): - """ + """ Return the balance of any set of move lines. Not to be confused with the 'balance' field on this model, which @@ -617,9 +678,6 @@ class account_move_line(orm.Model): if not ids: return total for line in self.read( - cr, uid, ids, ['debit', 'credit'], context=context): + cr, uid, ids, ['debit', 'credit'], context=context): total += (line['debit'] or 0.0) - (line['credit'] or 0.0) return total -account_move_line() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/banking_import_transaction.py b/account_banking/banking_import_transaction.py index c09a9c30b..abcfced01 100644 --- a/account_banking/banking_import_transaction.py +++ b/account_banking/banking_import_transaction.py @@ -30,7 +30,6 @@ from openerp.tools.translate import _ from openerp.addons.decimal_precision import decimal_precision as dp from openerp.addons.account_banking.parsers import models from openerp.addons.account_banking.parsers import convert -from openerp.addons.account_banking import sepa from openerp.addons.account_banking.wizard import banktools bt = models.mem_bank_transaction @@ -48,8 +47,9 @@ class banking_import_transaction(orm.Model): _description = 'Bank import transaction' _rec_name = 'transaction' - # This variable is used to match supplier invoices with an invoice date after - # the real payment date. This can occur with online transactions (web shops). + # This variable is used to match supplier invoices with an invoice date + # after the real payment date. This can occur with online transactions + # (web shops). # TODO: Convert this to a proper configuration variable payment_window = datetime.timedelta(days=10) @@ -65,7 +65,9 @@ class banking_import_transaction(orm.Model): amount = round(abs(trans.statement_line_id.amount), digits) # Make sure to be able to pinpoint our costs invoice for later # matching - reference = '%s.%s: %s' % (trans.statement, trans.transaction, trans.reference) + reference = '%s.%s: %s' % (trans.statement, + trans.transaction, + trans.reference) # search supplier invoice invoice_obj = self.pool.get('account.invoice') @@ -85,28 +87,29 @@ class banking_import_transaction(orm.Model): # create supplier invoice partner_obj = self.pool.get('res.partner') invoice_lines = [(0, 0, dict( - amount = 1, - price_unit = amount, - name = trans.message or trans.reference, - account_id = account_info.costs_account_id.id + amount=1, + price_unit=amount, + name=trans.message or trans.reference, + account_id=account_info.costs_account_id.id ))] invoice_address_id = partner_obj.address_get( cr, uid, [account_info.bank_partner_id.id], ['invoice'] ) invoice_id = invoice_obj.create(cr, uid, dict( - type = 'in_invoice', - company_id = account_info.company_id.id, - partner_id = account_info.bank_partner_id.id, - address_invoice_id = invoice_address_id['invoice'], - period_id = period_id, - journal_id = account_info.invoice_journal_id.id, - account_id = account_info.bank_partner_id.property_account_payable.id, - date_invoice = trans.execution_date, - reference_type = 'none', - reference = reference, - name = trans.reference or trans.message, - check_total = amount, - invoice_line = invoice_lines, + type='in_invoice', + company_id=account_info.company_id.id, + partner_id=account_info.bank_partner_id.id, + address_invoice_id=invoice_address_id['invoice'], + period_id=period_id, + journal_id=account_info.invoice_journal_id.id, + account_id=( + account_info.bank_partner_id.property_account_payable.id), + date_invoice=trans.execution_date, + reference_type='none', + reference=reference, + name=trans.reference or trans.message, + check_total=amount, + invoice_line=invoice_lines, )) invoice = invoice_obj.browse(cr, uid, invoice_id) # Create workflow @@ -129,8 +132,8 @@ class banking_import_transaction(orm.Model): Use the sales journal to check. Challenges we're facing: - 1. The sending or receiving party is not necessarily the same as the - partner the payment relates to. + 1. The sending or receiving party is not necessarily the same as + the partner the payment relates to. 2. References can be messed up during manual encoding and inexact matching can link the wrong invoices. 3. Amounts can or can not match the expected amount. @@ -138,8 +141,8 @@ class banking_import_transaction(orm.Model): .. There are countless more, but these we'll try to address. Assumptions for matching: - 1. There are no payments for invoices not sent. These are dealt with - later on. + 1. There are no payments for invoices not sent. These are dealt + with later on. 2. Debit amounts are either customer invoices or credited supplier invoices. 3. Credit amounts are either supplier invoices or credited customer @@ -152,8 +155,8 @@ class banking_import_transaction(orm.Model): 1. No match was made. No harm done. Proceed with manual matching as usual. 2. The wrong match was made. - Statements are encoded in draft. You will have the opportunity to - manually correct the wrong assumptions. + Statements are encoded in draft. You will have the opportunity + to manually correct the wrong assumptions. TODO: REVISE THIS DOC #Return values: @@ -170,8 +173,10 @@ class banking_import_transaction(orm.Model): ''' Return the eyecatcher for an invoice ''' - return invoice.type.startswith('in_') and invoice.name or \ - invoice.number + if invoice.type.startswith('in_'): + return invoice.name or invoice.number + else: + return invoice.number def has_id_match(invoice, ref, msg): ''' @@ -197,7 +202,8 @@ class banking_import_transaction(orm.Model): iname = invoice.name.upper() if iname in ref or iname in msg: return True - if invoice.supplier_invoice_number and len(invoice.supplier_invoice_number) > 2: + if (invoice.supplier_invoice_number + and len(invoice.supplier_invoice_number) > 2): supp_ref = invoice.supplier_invoice_number.upper() if supp_ref in ref or supp_ref in msg: return True @@ -214,8 +220,8 @@ class banking_import_transaction(orm.Model): # the interactive wizard return False - #'''Check if the move_line has been cached''' - #return move_line.id in linked_invoices + # '''Check if the move_line has been cached''' + # return move_line.id in linked_invoices def _cache(move_line, remaining=0.0): '''Cache the move_line''' @@ -228,11 +234,12 @@ class banking_import_transaction(orm.Model): def _sign(invoice): '''Return the direction of an invoice''' - return {'in_invoice': -1, - 'in_refund': 1, - 'out_invoice': 1, - 'out_refund': -1 - }[invoice.type] + return { + 'in_invoice': -1, + 'in_refund': 1, + 'out_invoice': 1, + 'out_refund': -1 + }[invoice.type] def is_zero(move_line, total): return self.pool.get('res.currency').is_zero( @@ -256,9 +263,9 @@ class banking_import_transaction(orm.Model): # Next on reference/invoice number. Mind that this uses the invoice # itself, as the move_line references have been fiddled with on invoice - # creation. This also enables us to search for the invoice number in the - # reference instead of the other way around, as most human interventions - # *add* text. + # creation. This also enables us to search for the invoice number in + # the reference instead of the other way around, as most human + # interventions *add* text. ref = trans.reference.upper() msg = trans.message.upper() if len(candidates) > 1 or not candidates: @@ -280,15 +287,15 @@ class banking_import_transaction(orm.Model): # partners. if not candidates and partner_ids: candidates = [ - x for x in move_lines - if (is_zero(x.move_id, ((x.debit or 0.0) - (x.credit or 0.0)) - - trans.statement_line_id.amount) - and convert.str2date(x.date, '%Y-%m-%d') <= - (convert.str2date(trans.execution_date, '%Y-%m-%d') + - self.payment_window) - and (not _cached(x) or _remaining(x)) - and x.partner_id.id in partner_ids) - ] + x for x in move_lines + if (is_zero(x.move_id, ((x.debit or 0.0) - (x.credit or 0.0)) - + trans.statement_line_id.amount) + and convert.str2date(x.date, '%Y-%m-%d') <= + (convert.str2date(trans.execution_date, '%Y-%m-%d') + + self.payment_window) + and (not _cached(x) or _remaining(x)) + and x.partner_id.id in partner_ids) + ] move_line = False @@ -297,13 +304,14 @@ class banking_import_transaction(orm.Model): # amounts expected and received. # # TODO: currency coercing - best = [x for x in candidates - if (is_zero(x.move_id, ((x.debit or 0.0) - (x.credit or 0.0)) - - trans.statement_line_id.amount) - and convert.str2date(x.date, '%Y-%m-%d') <= - (convert.str2date(trans.execution_date, '%Y-%m-%d') + - self.payment_window)) - ] + best = [ + x for x in candidates + if (is_zero(x.move_id, ((x.debit or 0.0) - (x.credit or 0.0)) - + trans.statement_line_id.amount) + and convert.str2date(x.date, '%Y-%m-%d') <= + (convert.str2date(trans.execution_date, '%Y-%m-%d') + + self.payment_window)) + ] if len(best) == 1: # Exact match move_line = best[0] @@ -317,21 +325,22 @@ class banking_import_transaction(orm.Model): elif len(candidates) > 1: # Before giving up, check cache for catching duplicate # transfers first - paid = [x for x in move_lines - if x.invoice and has_id_match(x.invoice, ref, msg) - and convert.str2date(x.invoice.date_invoice, '%Y-%m-%d') - <= convert.str2date(trans.execution_date, '%Y-%m-%d') - and (_cached(x) and not _remaining(x)) - ] + paid = [ + x for x in move_lines + if x.invoice and has_id_match(x.invoice, ref, msg) + and convert.str2date(x.invoice.date_invoice, '%Y-%m-%d') + <= convert.str2date(trans.execution_date, '%Y-%m-%d') + and (_cached(x) and not _remaining(x)) + ] if paid: log.append( _('Unable to link transaction id %(trans)s ' '(ref: %(ref)s) to invoice: ' 'invoice %(invoice)s was already paid') % { - 'trans': '%s.%s' % (trans.statement, trans.transaction), + 'trans': '%s.%s' % (trans.statement, + trans.transaction), 'ref': trans.reference, - 'invoice': eyecatcher(paid[0].invoice) - }) + 'invoice': eyecatcher(paid[0].invoice)}) else: # Multiple matches # TODO select best bank account in this case @@ -356,8 +365,8 @@ class banking_import_transaction(orm.Model): # Last partial payment will not flag invoice paid without # manual assistence # Stefan: disabled this here for the interactive method - # Handled this with proper handling of partial reconciliation - # and the workflow service + # Handled this with proper handling of partial + # reconciliation and the workflow service # invoice_obj = self.pool.get('account.invoice') # invoice_obj.write(cr, uid, [invoice.id], { # 'state': 'paid' @@ -369,7 +378,7 @@ class banking_import_transaction(orm.Model): account_ids = [ x.id for x in bank_account_ids if x.partner_id.id == move_line.partner_id.id - ] + ] return (trans, self._get_move_info( cr, uid, [move_line.id], @@ -396,20 +405,22 @@ class banking_import_transaction(orm.Model): _("Cannot link transaction %s with invoice") % transaction.statement_line_id.name, (transaction.invoice_ids and - (_("Please select one of the matches in transaction %s.%s") or - _("No match found for transaction %s.%s")) % ( - transaction.statement_line_id.statement_id.name, - transaction.statement_line_id.name + (_("Please select one of the matches in transaction " + "%s.%s") or + _("No match found for transaction %s.%s")) % ( + transaction.statement_line_id.statement_id.name, + transaction.statement_line_id.name ))) else: raise orm.except_orm( _("Cannot link transaction %s with accounting entry") % transaction.statement_line_id.name, (transaction.move_line_ids and - (_("Please select one of the matches in transaction %s.%s") or - _("No match found for transaction %s.%s")) % ( - transaction.statement_line_id.statement_id.name, - transaction.statement_line_id.name + (_("Please select one of the matches in transaction " + "%s.%s") or + _("No match found for transaction %s.%s")) % ( + transaction.statement_line_id.statement_id.name, + transaction.statement_line_id.name ))) st_line = transaction.statement_line_id @@ -458,9 +469,10 @@ class banking_import_transaction(orm.Model): # Define the voucher voucher = { 'journal_id': st_line.statement_id.journal_id.id, - 'partner_id': st_line.partner_id and st_line.partner_id.id or False, + 'partner_id': ( + st_line.partner_id and st_line.partner_id.id or False), 'company_id': st_line.company_id.id, - 'type':voucher_type, + 'type': voucher_type, 'account_id': account_id, 'amount': abs(st_line.amount), 'writeoff_amount': writeoff, @@ -470,12 +482,12 @@ class banking_import_transaction(orm.Model): 'date': st_line.date, 'date_due': st_line.date, 'period_id': period_id, - 'payment_rate_currency_id':to_curr_id, - } + 'payment_rate_currency_id': to_curr_id, + } # Define the voucher line vch_line = { - #'voucher_id': v_id, + # 'voucher_id': v_id, 'move_line_id': transaction.move_line_id.id, 'reconcile': True, 'amount': line_amount, @@ -490,7 +502,8 @@ class banking_import_transaction(orm.Model): {'voucher_id': voucher_id}, context=context) transaction.refresh() - def _legacy_do_move_unreconcile(self, cr, uid, move_line_ids, currency, context=None): + def _legacy_do_move_unreconcile(self, cr, uid, move_line_ids, currency, + context=None): """ Legacy method. Allow for canceling bank statement lines that were confirmed using earlier versions of the interactive wizard branch. @@ -508,9 +521,14 @@ class banking_import_transaction(orm.Model): reconcile_obj = self.pool.get('account.move.reconcile') is_zero = lambda amount: self.pool.get('res.currency').is_zero( cr, uid, currency, amount) - move_lines = move_line_obj.browse(cr, uid, move_line_ids, context=context) - reconcile = move_lines[0].reconcile_id or move_lines[0].reconcile_partial_id - line_ids = [x.id for x in reconcile.line_id or reconcile.line_partial_ids] + move_lines = move_line_obj.browse(cr, uid, move_line_ids, + context=context) + reconcile = (move_lines[0].reconcile_id + or move_lines[0].reconcile_partial_id) + line_ids = [ + x.id + for x in reconcile.line_id or reconcile.line_partial_ids + ] for move_line_id in move_line_ids: line_ids.remove(move_line_id) if len(line_ids) > 1: @@ -522,16 +540,18 @@ class banking_import_transaction(orm.Model): line_ids = [] reconcile_obj.write( cr, uid, reconcile.id, - { 'line_partial_ids': [(6, 0, line_partial_ids)], - 'line_id': [(6, 0, line_ids)], - }, context=context) + {'line_partial_ids': [(6, 0, line_partial_ids)], + 'line_id': [(6, 0, line_ids)], + }, context=context) else: reconcile_obj.unlink(cr, uid, reconcile.id, context=context) for move_line in move_lines: if move_line.invoice: # reopening the invoice netsvc.LocalService('workflow').trg_validate( - uid, 'account.invoice', move_line.invoice.id, 'undo_paid', cr) + uid, 'account.invoice', move_line.invoice.id, 'undo_paid', + cr + ) return True def _legacy_clear_up_writeoff(self, cr, uid, transaction, context=None): @@ -550,8 +570,7 @@ class banking_import_transaction(orm.Model): context=context) return True - def _legacy_cancel_move( - self, cr, uid, transaction, context=None): + def _legacy_cancel_move(self, cr, uid, transaction, context=None): """ Legacy method to support upgrades from older installations of the interactive wizard branch. @@ -590,16 +609,15 @@ class banking_import_transaction(orm.Model): cr, uid, transaction.statement_line_id.id, {'reconcile_id': False}, context=context) - def _cancel_voucher( - self, cr, uid, transaction_id, context=None): + def _cancel_voucher(self, cr, uid, transaction_id, context=None): voucher_pool = self.pool.get('account.voucher') transaction = self.browse(cr, uid, transaction_id, context=context) st_line = transaction.statement_line_id if transaction.match_type: if st_line.voucher_id: # Although vouchers can be associated with statement lines - # in standard OpenERP, we consider ourselves owner of the voucher - # if the line has an associated transaction + # in standard OpenERP, we consider ourselves owner of the + # voucher if the line has an associated transaction # Upon canceling of the statement line/transaction, # we cancel and delete the vouchers. # Otherwise, the statement line will leave the voucher @@ -610,7 +628,8 @@ class banking_import_transaction(orm.Model): cr, uid, [st_line.voucher_id.id], context=context) voucher_pool.unlink( cr, uid, [st_line.voucher_id.id], context=context) - if transaction.move_line_id and transaction.move_line_id.invoice: + if (transaction.move_line_id + and transaction.move_line_id.invoice): # reopening the invoice netsvc.LocalService('workflow').trg_validate( uid, 'account.invoice', @@ -639,7 +658,9 @@ class banking_import_transaction(orm.Model): _("No method found to cancel this type")) self.cancel_map[transaction.match_type]( self, cr, uid, transaction.id, context) - self._legacy_clear_up_writeoff(cr, uid, transaction, context=context) + self._legacy_clear_up_writeoff( + cr, uid, transaction, context=context + ) return True confirm_map = { @@ -665,7 +686,7 @@ class banking_import_transaction(orm.Model): if transaction.match_type not in ('invoice', 'move', 'manual'): raise orm.except_orm( _("Cannot reconcile"), - _("Bank transaction %s: write off not implemented for " + + _("Bank transaction %s: write off not implemented for " "this match type.") % transaction.statement_line_id.name ) @@ -701,14 +722,14 @@ class banking_import_transaction(orm.Model): # due to float representation and rounding difficulties for trans in self.browse(cr, uid, ids, context=context): if self.pool.get('res.currency').is_zero( - cr, uid, - trans.statement_id.currency, - me['transferred_amount'] - trans.transferred_amount): + cr, uid, + trans.statement_id.currency, + me['transferred_amount'] - trans.transferred_amount): dupes.append(trans.id) if len(dupes) < 1: raise orm.except_orm(_('Cannot check for duplicate'), - _("Cannot check for duplicate. " - "I can't find myself.")) + _("Cannot check for duplicate. " + "I can't find myself.")) if len(dupes) > 1: self.write( cr, uid, res, {'duplicate': True}, context=context) @@ -723,7 +744,7 @@ class banking_import_transaction(orm.Model): pass def _get_move_info(self, cr, uid, move_line_ids, partner_bank_id=False, - partial=False, match_type = False): + partial=False, match_type=False, context=None): type_map = { 'out_invoice': 'customer', 'in_invoice': 'supplier', @@ -738,7 +759,9 @@ class banking_import_transaction(orm.Model): 'match_type': match_type, 'account_id': False, } - move_lines = self.pool.get('account.move.line').browse(cr, uid, move_line_ids) + move_lines = self.pool.get('account.move.line').browse( + cr, uid, move_line_ids, context=context + ) for move_line in move_lines: if move_line.partner_id: if retval['partner_id']: @@ -780,7 +803,7 @@ class banking_import_transaction(orm.Model): if move_lines and len(move_lines) == 1: retval['reference'] = move_lines[0].ref if retval['match_type'] == 'invoice': - retval['invoice_ids'] = list(set([x.invoice.id for x in move_lines])) + retval['invoice_ids'] = list(set(x.invoice.id for x in move_lines)) retval['type'] = type_map[move_lines[0].invoice.type] return retval @@ -790,14 +813,12 @@ class banking_import_transaction(orm.Model): vals['move_line_ids'] = [(6, 0, move_info.get('move_line_ids') or [])] vals['invoice_ids'] = [(6, 0, move_info.get('invoice_ids') or [])] vals['move_line_id'] = (move_info.get('move_line_ids', False) and - len(move_info['move_line_ids']) == 1 and - move_info['move_line_ids'][0] - ) + len(move_info['move_line_ids']) == 1 and + move_info['move_line_ids'][0]) if move_info['match_type'] == 'invoice': vals['invoice_id'] = (move_info.get('invoice_ids', False) and - len(move_info['invoice_ids']) == 1 and - move_info['invoice_ids'][0] - ) + len(move_info['invoice_ids']) == 1 and + move_info['invoice_ids'][0]) return vals def hook_match_payment(self, cr, uid, transaction, log, context=None): @@ -824,13 +845,13 @@ class banking_import_transaction(orm.Model): # Results if results is None: results = dict( - trans_loaded_cnt = 0, - trans_skipped_cnt = 0, - trans_matched_cnt = 0, - bank_costs_invoice_cnt = 0, - error_cnt = 0, - log = [], - ) + trans_loaded_cnt=0, + trans_skipped_cnt=0, + trans_matched_cnt=0, + bank_costs_invoice_cnt=0, + error_cnt=0, + log=[], + ) # Caching error_accounts = {} @@ -848,8 +869,9 @@ class banking_import_transaction(orm.Model): if has_payment: payment_line_ids = payment_line_obj.search( cr, uid, [ - ('order_id.state', '=', 'sent'), - ('date_done', '=', False)], context=context) + ('order_id.state', '=', 'sent'), + ('date_done', '=', False) + ], context=context) payment_lines = payment_line_obj.browse( cr, uid, payment_line_ids) @@ -885,25 +907,30 @@ class banking_import_transaction(orm.Model): # Get interesting journals once # Added type 'general' to capture fund transfers - journal_ids = journal_obj.search(cr, uid, [ - ('type', 'in', ('general', 'sale','purchase', - 'purchase_refund','sale_refund')), + journal_ids = journal_obj.search( + cr, uid, [ + ('type', 'in', ('general', 'sale', 'purchase', + 'purchase_refund', 'sale_refund')), ('company_id', '=', company.id), - ]) + ], + ) # Get all unreconciled moves - move_line_ids = move_line_obj.search(cr, uid, [ + move_line_ids = move_line_obj.search( + cr, uid, [ ('reconcile_id', '=', False), ('journal_id', 'in', journal_ids), ('account_id.reconcile', '=', True), ('date', '<=', transaction.execution_date), - ]) + ], + ) if move_line_ids: move_lines = move_line_obj.browse(cr, uid, move_line_ids) else: move_lines = [] # Create fallback currency code - currency_code = transaction.local_currency or company.currency_id.name + currency_code = (transaction.local_currency + or company.currency_id.name) # Check cache for account info/currency if transaction.local_account in info and \ @@ -917,7 +944,8 @@ class banking_import_transaction(orm.Model): ) if not account_info: results['log'].append( - _('Transaction found for unknown account %(bank_account)s') % + _('Transaction found for unknown account ' + '%(bank_account)s') % {'bank_account': transaction.local_account} ) error_accounts[transaction.local_account] = True @@ -929,7 +957,7 @@ class banking_import_transaction(orm.Model): results['log'].append( _('Transaction found for account %(bank_account)s, ' 'but no default journal was defined.' - ) % {'bank_account': transaction.local_account} + ) % {'bank_account': transaction.local_account} ) error_accounts[transaction.local_account] = True results['error_cnt'] += 1 @@ -941,12 +969,14 @@ class banking_import_transaction(orm.Model): currency_code = account_info.currency_id.name # Cache results - if not transaction.local_account in info: + if transaction.local_account not in info: info[transaction.local_account] = { currency_code: account_info } else: - info[transaction.local_account][currency_code] = account_info + info[transaction.local_account][currency_code] = ( + account_info + ) # Link accounting period period_id = banktools.get_period( @@ -965,7 +995,8 @@ class banking_import_transaction(orm.Model): _("Cannot perform match on a confirmed transction")) else: values = { - 'name': '%s.%s' % (transaction.statement, transaction.transaction), + 'name': '%s.%s' % (transaction.statement, + transaction.transaction), 'date': transaction.execution_date, 'amount': transaction.transferred_amount, 'statement_id': transaction.statement_id.id, @@ -979,7 +1010,9 @@ class banking_import_transaction(orm.Model): account_info.default_credit_account_id.id or account_info.default_debit_account_id.id), } - statement_line_id = statement_line_obj.create(cr, uid, values, context) + statement_line_id = statement_line_obj.create( + cr, uid, values, context + ) results['trans_loaded_cnt'] += 1 transaction.write({'statement_line_id': statement_line_id}) transaction.refresh() @@ -991,13 +1024,14 @@ class banking_import_transaction(orm.Model): and account_info.currency_id.name != transaction.local_currency: # TODO: convert currencies? results['log'].append( - _('transaction %(statement_id)s.%(transaction_id)s for account %(bank_account)s' - ' uses different currency than the defined bank journal.' - ) % { - 'bank_account': transactions.local_account, - 'statement_id': transaction.statement, - 'transaction_id': transaction.transaction, - } + _('transaction %(statement_id)s.%(transaction_id)s for ' + 'account %(bank_account)s uses different currency than ' + 'the defined bank journal.' + ) % { + 'bank_account': transactions.local_account, + 'statement_id': transaction.statement, + 'transaction_id': transaction.transaction, + } ) error_accounts[transaction.local_account] = True results['error_cnt'] += 1 @@ -1006,17 +1040,18 @@ class banking_import_transaction(orm.Model): continue # When bank costs are part of transaction itself, split it. - if transaction.type != bt.BANK_COSTS and transaction.provision_costs: + if (transaction.type != bt.BANK_COSTS + and transaction.provision_costs): # Create new transaction for bank costs cost_id = self.copy( cr, uid, transaction.id, dict( - type = bt.BANK_COSTS, - transaction = '%s-prov' % transaction.transaction, - transferred_amount = transaction.provision_costs, - remote_currency = transaction.provision_costs_currency, - message = transaction.provision_costs_description, - parent_id = transaction.id, + type=bt.BANK_COSTS, + transaction='%s-prov' % transaction.transaction, + transferred_amount=transaction.provision_costs, + remote_currency=transaction.provision_costs_currency, + message=transaction.provision_costs_description, + parent_id=transaction.id, ), context) injected.append(self.browse(cr, uid, cost_id, context)) @@ -1028,14 +1063,17 @@ class banking_import_transaction(orm.Model): self.write( cr, uid, transaction.id, dict( - transferred_amount = - transaction.transferred_amount - transaction.provision_costs, - provision_costs = False, - provision_costs_currency = False, - provision_costs_description = False, + transferred_amount=( + transaction.transferred_amount - + transaction.provision_costs), + provision_costs=False, + provision_costs_currency=False, + provision_costs_description=False, ), context=context) # rebrowse the current record after writing - transaction = self.browse(cr, uid, transaction.id, context=context) + transaction = self.browse( + cr, uid, transaction.id, context=context + ) # Match payment and direct debit orders move_info_payment = self.hook_match_payment( @@ -1088,7 +1126,8 @@ class banking_import_transaction(orm.Model): # Credit means payment... isn't it? if (not move_info - and transaction.statement_line_id.amount < 0 and payment_lines): + and transaction.statement_line_id.amount < 0 + and payment_lines): # Link open payment - if any # Note that _match_payment is defined in the # account_banking_payment module which should be installed @@ -1146,10 +1185,13 @@ class banking_import_transaction(orm.Model): values['type'] = move_info['type'] else: values['partner_id'] = values['partner_bank_id'] = False - if not values['partner_id'] and partner_ids and len(partner_ids) == 1: + if (not values['partner_id'] + and partner_ids + and len(partner_ids) == 1): values['partner_id'] = partner_ids[0] - if (not values['partner_bank_id'] and partner_banks and - len(partner_banks) == 1): + if (not values['partner_bank_id'] + and partner_banks + and len(partner_banks) == 1): values['partner_bank_id'] = partner_banks[0].id statement_line_obj.write( @@ -1158,7 +1200,7 @@ class banking_import_transaction(orm.Model): if not injected: i += 1 - #recompute statement end_balance for validation + # recompute statement end_balance for validation if imported_statement_ids: statement_obj.button_dummy( cr, uid, imported_statement_ids, context=context) @@ -1185,7 +1227,7 @@ class banking_import_transaction(orm.Model): res = dict([(x, False) for x in ids]) for transaction in self.browse(cr, uid, ids, context): if (transaction.statement_line_id.state == 'draft' and - not(transaction.move_currency_amount is False)): + not(transaction.move_currency_amount is False)): res[transaction.id] = ( transaction.move_currency_amount - transaction.statement_line_id.amount @@ -1214,22 +1256,32 @@ class banking_import_transaction(orm.Model): Write values in argument 'vals', but clear all match related values first """ - write_vals = (dict([(x, False) for x in [ + write_vals = ( + dict([ + (x, False) + for x in [ 'match_type', 'move_line_id', 'invoice_id', - ]] + - [(x, [(6, 0, [])]) for x in [ - 'move_line_ids', - 'invoice_ids', - ]])) + ] + ] + [ + (x, [(6, 0, [])]) + for x in [ + 'move_line_ids', + 'invoice_ids', + ] + ] + ) + ) write_vals.update(vals or {}) return self.write(cr, uid, ids, write_vals, context=context) def _get_move_amount(self, cr, uid, ids, name, args, context=None): """ - Need to get the residual amount on the move (invoice) in the bank statement currency. - This will be used to calculate the write-off amount (in statement currency). + Need to get the residual amount on the move (invoice) in the bank + statement currency. + This will be used to calculate the write-off amount + (in statement currency). """ if not ids: return {} @@ -1240,20 +1292,25 @@ class banking_import_transaction(orm.Model): for transaction in self.browse(cr, uid, ids, context): if transaction.move_line_id: - move_line_amount = transaction.move_line_id.amount_residual_currency + move_line_amount = ( + transaction.move_line_id.amount_residual_currency) + statement = transaction.statement_line_id.statement_id to_curr_id = ( - transaction.statement_line_id.statement_id.journal_id.currency - and transaction.statement_line_id.statement_id.journal_id.currency.id - or transaction.statement_line_id.statement_id.company_id.currency_id.id + statement.journal_id.currency + and statement.journal_id.currency.id + or statement.company_id.currency_id.id ) from_curr_id = ( transaction.move_line_id.currency_id and transaction.move_line_id.currency_id.id - or transaction.statement_line_id.statement_id.company_id.currency_id.id + or statement.company_id.currency_id.id ) if from_curr_id != to_curr_id: - amount_currency = stline_pool._convert_currency(cr, uid, from_curr_id, to_curr_id, move_line_amount, round=True, - date=transaction.statement_line_id.date, context=context) + amount_currency = stline_pool._convert_currency( + cr, uid, from_curr_id, to_curr_id, move_line_amount, + round=True, date=transaction.statement_line_id.date, + context=context + ) else: amount_currency = move_line_amount sign = 1 @@ -1261,7 +1318,8 @@ class banking_import_transaction(orm.Model): if transaction.move_line_id.amount_currency < 0: sign = -1 else: - if (transaction.move_line_id.debit - transaction.move_line_id.credit) < 0: + if (transaction.move_line_id.debit + - transaction.move_line_id.credit) < 0: sign = -1 res[transaction.id] = sign * amount_currency @@ -1274,13 +1332,15 @@ class banking_import_transaction(orm.Model): for this in self.browse(cr, uid, ids, context): if this.parent_id: this.parent_id.write( - {'transferred_amount': - this.parent_id.transferred_amount + \ - this.transferred_amount, - }) + { + 'transferred_amount': + this.parent_id.transferred_amount + + this.transferred_amount, + } + ) this.parent_id.refresh() return super(banking_import_transaction, self).unlink( - cr, uid, ids, context=context) + cr, uid, ids, context=context) column_map = { # used in bank_import.py, converting non-osv transactions @@ -1291,8 +1351,8 @@ class banking_import_transaction(orm.Model): _columns = { # start mem_bank_transaction atributes # see parsers/models.py - 'transaction': fields.char('transaction', size=16), # id - 'statement': fields.char('statement', size=16), # statement_id + 'transaction': fields.char('transaction', size=16), # id + 'statement': fields.char('statement', size=16), # statement_id 'type': fields.char('type', size=16), 'reference': fields.char('reference', size=1024), 'local_account': fields.char('local_account', size=24), @@ -1307,8 +1367,14 @@ class banking_import_transaction(orm.Model): 'remote_owner': fields.char('remote_owner', size=128), 'remote_owner_address': fields.char('remote_owner_address', size=256), 'remote_owner_city': fields.char('remote_owner_city', size=128), - 'remote_owner_postalcode': fields.char('remote_owner_postalcode', size=24), - 'remote_owner_country_code': fields.char('remote_owner_country_code', size=24), + 'remote_owner_postalcode': fields.char( + 'remote_owner_postalcode', + size=24, + ), + 'remote_owner_country_code': fields.char( + 'remote_owner_country_code', + size=24, + ), 'remote_owner_custno': fields.char('remote_owner_custno', size=24), 'remote_bank_bic': fields.char('remote_bank_bic', size=24), 'remote_bank_bei': fields.char('remote_bank_bei', size=24), @@ -1318,8 +1384,14 @@ class banking_import_transaction(orm.Model): 'remote_bank_duns': fields.char('remote_bank_duns', size=24), 'remote_bank_tax_id': fields.char('remote_bank_tax_id', size=24), 'provision_costs': fields.float('provision_costs', size=24), - 'provision_costs_currency': fields.char('provision_costs_currency', size=64), - 'provision_costs_description': fields.char('provision_costs_description', size=24), + 'provision_costs_currency': fields.char( + 'provision_costs_currency', + size=64, + ), + 'provision_costs_description': fields.char( + 'provision_costs_description', + size=24, + ), 'error_message': fields.char('error_message', size=1024), 'storno_retry': fields.boolean('storno_retry'), # end of mem_bank_transaction_fields @@ -1341,15 +1413,15 @@ class banking_import_transaction(orm.Model): 'banking.import.transaction', 'Split off from this transaction'), # match fields 'match_type': fields.selection([ - ('move','Move'), - ('invoice', 'Invoice'), - ('payment', 'Payment line'), - ('payment_order', 'Payment order'), - ('storno', 'Storno'), - ('manual', 'Manual'), - ('payment_manual', 'Payment line (manual)'), - ('payment_order_manual', 'Payment order (manual)'), - ], 'Match type'), + ('move', 'Move'), + ('invoice', 'Invoice'), + ('payment', 'Payment line'), + ('payment_order', 'Payment order'), + ('storno', 'Storno'), + ('manual', 'Manual'), + ('payment_manual', 'Payment line (manual)'), + ('payment_order_manual', 'Payment order (manual)'), + ], 'Match type'), 'match_multi': fields.function( _get_match_multi, method=True, string='Multi match', type='boolean'), @@ -1366,13 +1438,16 @@ class banking_import_transaction(orm.Model): 'residual': fields.function( _get_residual, method=True, string='Residual', type='float'), 'writeoff_account_id': fields.many2one( - 'account.account', 'Write-off account', - domain=[('type', '!=', 'view')]), - 'payment_option':fields.selection( + 'account.account', + 'Write-off account', + domain=[('type', '!=', 'view')] + ), + 'payment_option': fields.selection( [ ('without_writeoff', 'Keep Open'), ('with_writeoff', 'Reconcile Payment Balance') - ], 'Payment Difference', + ], + 'Payment Difference', required=True, help=("This field helps you to choose what you want to do with " "the eventual difference between the paid amount and the " @@ -1387,18 +1462,20 @@ class banking_import_transaction(orm.Model): 'writeoff_analytic_id': fields.many2one( 'account.analytic.account', 'Write off analytic account'), 'move_currency_amount': fields.function( - _get_move_amount, method=True, string='Match Amount', type='float'), - } + _get_move_amount, + method=True, + string='Match Amount', + type='float', + ), + } _defaults = { - 'company_id': lambda s,cr,uid,c: + 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get( - cr, uid, 'bank.import.transaction', context=c), + cr, uid, 'bank.import.transaction', context=c), 'payment_option': 'without_writeoff', } -banking_import_transaction() - class account_bank_statement_line(orm.Model): _inherit = 'account.bank.statement.line' @@ -1436,7 +1513,7 @@ class account_bank_statement_line(orm.Model): 'match_type': fields.related( 'import_transaction_id', 'match_type', type='selection', selection=[ - ('move','Move'), + ('move', 'Move'), ('invoice', 'Invoice'), ('payment', 'Payment line'), ('payment_order', 'Payment order'), @@ -1444,13 +1521,15 @@ class account_bank_statement_line(orm.Model): ('manual', 'Manual'), ('payment_manual', 'Payment line (manual)'), ('payment_order_manual', 'Payment order (manual)'), - ], + ], string='Match type', readonly=True,), 'state': fields.selection( [('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State', readonly=True, required=True), - 'parent_id': fields.many2one('account.bank.statement.line', - 'Parent'), + 'parent_id': fields.many2one( + 'account.bank.statement.line', + 'Parent', + ), 'link_partner_ok': fields.function( _get_link_partner_ok, type='boolean', string='Can link partner'), @@ -1471,7 +1550,9 @@ class account_bank_statement_line(orm.Model): wizard_obj = self.pool.get('banking.transaction.wizard') res_id = wizard_obj.create( cr, uid, {'statement_line_id': ids[0]}, context=context) - res = wizard_obj.create_act_window(cr, uid, res_id, context=context) + res = wizard_obj.create_act_window( + cr, uid, res_id, context=context + ) return res def link_partner(self, cr, uid, ids, context=None): @@ -1499,8 +1580,8 @@ class account_bank_statement_line(orm.Model): {'partner_id': statement_line.partner_bank_id.partner_id.id}) return True - if (not statement_line.import_transaction_id or - not statement_line.import_transaction_id.remote_account): + if (not statement_line.import_transaction_id + or not statement_line.import_transaction_id.remote_account): raise orm.except_orm( _("Error"), _("No bank account available to link partner to")) @@ -1535,8 +1616,8 @@ class account_bank_statement_line(orm.Model): return wizard_obj.create_act_window(cr, uid, res_id, context=context) def _convert_currency( - self, cr, uid, from_curr_id, to_curr_id, from_amount, - round=False, date=None, context=None): + self, cr, uid, from_curr_id, to_curr_id, from_amount, + round=False, date=None, context=None): """Convert currency amount using the company rate on a specific date""" curr_obj = self.pool.get('res.currency') if context: @@ -1555,8 +1636,9 @@ class account_bank_statement_line(orm.Model): """ Create (or update) a voucher for each statement line, and then generate the moves by posting the voucher. - If a line does not have a move line against it, but has an account, then - generate a journal entry that moves the line amount to the specified account. + If a line does not have a move line against it, but has an account, + then generate a journal entry that moves the line amount to the + specified account. """ statement_pool = self.pool.get('account.bank.statement') obj_seq = self.pool.get('ir.sequence') @@ -1576,7 +1658,8 @@ class account_bank_statement_line(orm.Model): raise orm.except_orm( _('No Analytic Journal !'), _("You have to define an analytic journal on the '%s' " - "journal!") % (st_line.statement_id.journal_id.name,)) + "journal!") % st_line.statement_id.journal_id.name + ) if not st_line.amount: continue if not st_line.period_id: @@ -1594,10 +1677,16 @@ class account_bank_statement_line(orm.Model): if st.journal_id.sequence_id: period = st.period_id or st_line.period_id c = {'fiscalyear_id': period.fiscalyear_id.id} - st_number = obj_seq.next_by_id(cr, uid, st.journal_id.sequence_id.id, context=c) + 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') - statement_pool.write(cr, uid, [st.id], {'name': st_number}, context=context) + st_number = obj_seq.next_by_code( + cr, uid, 'account.bank.statement' + ) + statement_pool.write( + cr, uid, [st.id], {'name': st_number}, context=context + ) if st_line.import_transaction_id: import_transaction_obj.confirm( @@ -1607,7 +1696,9 @@ class account_bank_statement_line(orm.Model): cr, uid, st_number, st_line, context) company_currency_id = st.journal_id.company_id.currency_id.id statement_pool.create_move_from_st_line( - cr, uid, st_line.id, company_currency_id, st_line_number, context) + cr, uid, st_line.id, company_currency_id, st_line_number, + context + ) self.write( cr, uid, st_line.id, {'state': 'confirmed'}, context) return True @@ -1626,15 +1717,17 @@ class account_bank_statement_line(orm.Model): if st_line.statement_id.state != 'draft': raise orm.except_orm( _("Cannot cancel bank transaction"), - _("The bank statement that this transaction belongs to has " - "already been confirmed")) + _("The bank statement that this transaction belongs to " + "has already been confirmed")) if st_line.import_transaction_id: # Cancel transaction immediately. # If it has voucher, this will clean up # the moves on the st_line. import_transaction_obj.cancel( - cr, uid, [st_line.import_transaction_id.id], context=context) + cr, uid, [st_line.import_transaction_id.id], + context=context + ) st_line.refresh() for line in st_line.move_ids: # We allow for people canceling and removing @@ -1650,7 +1743,6 @@ class account_bank_statement_line(orm.Model): cr, uid, set_draft_ids, {'state': 'draft'}, context=context) return True - def unlink(self, cr, uid, ids, context=None): """ Don't allow deletion of a confirmed statement line @@ -1667,15 +1759,15 @@ class account_bank_statement_line(orm.Model): ": '%s'") % line.name) if line.parent_id: line.parent_id.write( - { - 'amount': line.parent_id.amount + line.amount, - }) + { + 'amount': line.parent_id.amount + line.amount, + } + ) line.parent_id.refresh() return super(account_bank_statement_line, self).unlink( cr, uid, ids, context=context) - def create_instant_transaction( - self, cr, uid, ids, context=None): + def create_instant_transaction(self, cr, uid, ids, context=None): """ Check for existance of import transaction on the bank statement lines. Create instant items if appropriate. @@ -1696,8 +1788,7 @@ class account_bank_statement_line(orm.Model): context = {} localcontext = context.copy() localcontext['transaction_no_duplicate_search'] = True - for line in self.browse( - cr, uid, ids, context=context): + for line in self.browse(cr, uid, ids, context=context): if line.state != 'confirmed' and not line.import_transaction_id: res = import_transaction_pool.create( cr, uid, { @@ -1723,23 +1814,23 @@ class account_bank_statement_line(orm.Model): child_statement_ids = [] for this in self.browse(cr, uid, ids, context): transaction_data = transaction_pool.copy_data( - cr, uid, this.import_transaction_id.id) + cr, uid, this.import_transaction_id.id + ) transaction_data['transferred_amount'] = amount - transaction_data['message'] = ( - (transaction_data['message'] or '') + _(' (split)')) + transaction_data['message'] = ((transaction_data['message'] or '') + + _(' (split)')) transaction_data['parent_id'] = this.import_transaction_id.id transaction_id = transaction_pool.create( - cr, - uid, - transaction_data, - context=dict( - context, transaction_no_duplicate_search=True)) + cr, + uid, + transaction_data, + context=dict(context, transaction_no_duplicate_search=True) + ) - statement_line_data = self.copy_data( - cr, uid, this.id) + statement_line_data = self.copy_data(cr, uid, this.id) statement_line_data['amount'] = amount statement_line_data['name'] = ( - (statement_line_data['name'] or '') + _(' (split)')) + (statement_line_data['name'] or '') + _(' (split)')) statement_line_data['import_transaction_id'] = transaction_id statement_line_data['parent_id'] = this.id statement_line_id = self.create( @@ -1785,35 +1876,43 @@ class account_bank_statement(orm.Model): line_obj = self.pool.get('account.bank.statement.line') for st in self.browse(cr, uid, ids, context=context): j_type = st.journal_id.type - if not self.check_status_condition(cr, uid, st.state, journal_type=j_type): + if not self.check_status_condition( + cr, uid, st.state, journal_type=j_type): continue - self.balance_check(cr, uid, st.id, journal_type=j_type, context=context) + self.balance_check(cr, uid, st.id, journal_type=j_type, + context=context) if (not st.journal_id.default_credit_account_id) \ or (not st.journal_id.default_debit_account_id): - raise orm.except_orm(_('Configuration Error !'), - _('Please verify that an account is defined in the journal.')) + raise orm.except_orm( + _('Configuration Error !'), + _('Please verify that an account is defined in the ' + 'journal.') + ) # protect against misguided manual changes for line in st.move_line_ids: if line.state != 'valid': - raise orm.except_orm(_('Error !'), - _('The account entries lines are not in valid state.')) + raise orm.except_orm( + _('Error !'), + _('The account entries lines are not in valid state.') + ) - line_obj.confirm(cr, uid, [line.id for line in st.line_ids], context) + line_obj.confirm(cr, uid, [line.id for line in st.line_ids], + context) st.refresh() self.message_post( cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st.name,), context=context) - return self.write(cr, uid, ids, {'state':'confirm'}, context=context) + return self.write(cr, uid, ids, {'state': 'confirm'}, context=context) def button_cancel(self, cr, uid, ids, context=None): """ Do nothing but write the state. Delegate all actions to the statement line workflow instead. """ - self.write(cr, uid, ids, {'state':'draft'}, context=context) + self.write(cr, uid, ids, {'state': 'draft'}, context=context) def unlink(self, cr, uid, ids, context=None): """ @@ -1841,5 +1940,3 @@ class account_bank_statement(orm.Model): 'balance_end': fields.function( _end_balance, method=True, store=True, string='Balance'), } - -account_bank_statement() diff --git a/account_banking/migrations/6.1.0.1.81/post-set-statement-line-state.py b/account_banking/migrations/6.1.0.1.81/post-set-statement-line-state.py index 59d65aa34..d471cdb16 100644 --- a/account_banking/migrations/6.1.0.1.81/post-set-statement-line-state.py +++ b/account_banking/migrations/6.1.0.1.81/post-set-statement-line-state.py @@ -4,8 +4,8 @@ # Copyright (C) 2011 Therp BV () # # 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 +# 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, @@ -24,10 +24,11 @@ __name__ = ("account.bank.statement.line:: set new field 'state' to " "confirmed for all statement lines belonging to confirmed " "statements") + def migrate(cr, version): - cr.execute ("UPDATE account_bank_statement_line as sl " - " SET state = 'confirmed'" - " FROM account_bank_statement as s " - " WHERE sl.statement_id = s.id " - " AND s.state = 'confirm' " - ) + cr.execute("UPDATE account_bank_statement_line as sl " + " SET state = 'confirmed'" + " FROM account_bank_statement as s " + " WHERE sl.statement_id = s.id " + " AND s.state = 'confirm' " + ) diff --git a/account_banking/migrations/7.0.0.1/pre-migration.py b/account_banking/migrations/7.0.0.1/pre-migration.py index e12284d0e..ef5656c66 100644 --- a/account_banking/migrations/7.0.0.1/pre-migration.py +++ b/account_banking/migrations/7.0.0.1/pre-migration.py @@ -19,6 +19,7 @@ # ############################################################################## + def migrate(cr, version): if not version: return @@ -26,7 +27,7 @@ def migrate(cr, version): # workflow state moved to another, new module cr.execute( """ - UPDATE ir_model_data + UPDATE ir_model_data SET module = 'account_banking_payment' WHERE name = 'trans_done_sent' AND module = 'account_direct_debit' diff --git a/account_banking/migrations/7.0.0.3/pre-migration.py b/account_banking/migrations/7.0.0.3/pre-migration.py index 0a1c80c6c..dab121c5d 100644 --- a/account_banking/migrations/7.0.0.3/pre-migration.py +++ b/account_banking/migrations/7.0.0.3/pre-migration.py @@ -19,6 +19,7 @@ # ############################################################################## + def migrate(cr, version): if not version: return diff --git a/account_banking/migrations/7.0.0.4/pre-migration.py b/account_banking/migrations/7.0.0.4/pre-migration.py index dfec20809..2abe042a6 100644 --- a/account_banking/migrations/7.0.0.4/pre-migration.py +++ b/account_banking/migrations/7.0.0.4/pre-migration.py @@ -19,6 +19,7 @@ # ############################################################################## + def table_exists(cr, table): """ Check whether a certain table or view exists """ cr.execute( @@ -26,6 +27,7 @@ def table_exists(cr, table): (table,)) return cr.fetchone()[0] == 1 + def migrate(cr, version): """ Migration script for semantic changes in account_banking_payment_export. diff --git a/account_banking/parsers/__init__.py b/account_banking/parsers/__init__.py index f79dea6c4..63d7ead96 100644 --- a/account_banking/parsers/__init__.py +++ b/account_banking/parsers/__init__.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -19,6 +19,6 @@ # ############################################################################## -import models +from . import models # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/parsers/convert.py b/account_banking/parsers/convert.py index 8287f4144..2578a0582 100644 --- a/account_banking/parsers/convert.py +++ b/account_banking/parsers/convert.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -29,14 +29,17 @@ try: except AttributeError: from mx import DateTime as datetime + def str2date(datestr, format='%d/%m/%y'): '''Convert a string to a datatime object''' return datetime.strptime(datestr, format) + def date2str(date, format='%Y-%m-%d'): '''Convert a datetime object to a string''' return date.strftime(format) + def date2date(datestr, fromfmt='%d/%m/%y', tofmt='%Y-%m-%d'): ''' Convert a date in a string to another string, in a different @@ -44,7 +47,9 @@ def date2date(datestr, fromfmt='%d/%m/%y', tofmt='%Y-%m-%d'): ''' return date2str(str2date(datestr, fromfmt), tofmt) -_SWIFT = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/-?:().,'+ " +_SWIFT = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "/-?:().,'+ ") + def to_swift(astr, schemes=['utf-8', 'latin-1', 'ascii']): ''' @@ -62,7 +67,7 @@ def to_swift(astr, schemes=['utf-8', 'latin-1', 'ascii']): s = [x in _SWIFT and x or ' ' for x in unicodedata.normalize('NFKD', astr).encode('ascii', 'ignore') - ] + ] return ''.join(s) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/parsers/models.py b/account_banking/parsers/models.py index 4e88ea66e..fbd72291f 100644 --- a/account_banking/parsers/models.py +++ b/account_banking/parsers/models.py @@ -22,6 +22,7 @@ import re from openerp.tools.translate import _ + class mem_bank_statement(object): ''' A mem_bank_statement is a real life projection of a bank statement paper @@ -34,9 +35,15 @@ class mem_bank_statement(object): ''' # Lock attributes to enable parsers to trigger non-conformity faults __slots__ = [ - 'start_balance','end_balance', 'date', 'local_account', - 'local_currency', 'id', 'transactions' + 'start_balance', + 'end_balance', + 'date', + 'local_account', + 'local_currency', + 'id', + 'transactions' ] + def __init__(self, *args, **kwargs): super(mem_bank_statement, self).__init__(*args, **kwargs) self.id = '' @@ -59,6 +66,7 @@ class mem_bank_statement(object): check += float(transaction.transferred_amount) return abs(check - float(self.end_balance)) < 0.0001 + class mem_bank_transaction(object): ''' A mem_bank_transaction is a real life copy of a bank transfer. Mapping to @@ -102,7 +110,7 @@ class mem_bank_transaction(object): # remote_currency 'transferred_amount', - # The actual amount transferred - + # The actual amount transferred - # negative means sent, positive means received # Most banks use the local_currency to express this amount, but there # may be exceptions I'm unaware of. @@ -126,7 +134,8 @@ class mem_bank_transaction(object): # The other parties postal code belonging to the address 'remote_owner_country_code', - # The other parties two letter ISO country code belonging to the previous + # The other parties two letter ISO country code belonging to the + # previous 'remote_owner_custno', # The other parties customer number @@ -175,7 +184,7 @@ class mem_bank_transaction(object): # An error message for interaction with the user # Only used when mem_transaction.valid returns False. 'error_message', - + # Storno attribute. When True, make the cancelled debit eligible for # a next direct debit run 'storno_retry', @@ -213,7 +222,7 @@ class mem_bank_transaction(object): # Will be selected for matching. # STORNO A failed or reversed attempt at direct debit. # Either due to an action on the payer's side - # or a failure observed by the bank (lack of + # or a failure observed by the bank (lack of # credit for instance) # # Perhaps more will follow. @@ -230,7 +239,7 @@ class mem_bank_transaction(object): DIRECT_DEBIT = 'DD' ORDER = 'DO' PAYMENT_BATCH = 'PB' - PAYMENT_TERMINAL = 'PT' + PAYMENT_TERMINAL = 'PT' PERIODIC_ORDER = 'PO' STORNO = 'ST' @@ -270,7 +279,7 @@ class mem_bank_transaction(object): if value in self.types: self.transfer_type = value else: - raise ValueError, _('Invalid value for transfer_type') + raise ValueError(_('Invalid value for transfer_type')) type = property(_get_type, _set_type) @@ -282,6 +291,7 @@ class mem_bank_transaction(object): return (self.execution_date and self.remote_account and self.transferred_amount and True) or False + class parser_type(type): ''' Meta annex factory class for house keeping and collecting parsers. @@ -314,11 +324,13 @@ class parser_type(type): keys.sort() return [(parsers[x].code, parsers[x].name) for x in keys] + def create_parser(code): if code in parser_type.parser_by_code: return parser_type.parser_by_code[code]() return None + class parser(object): ''' A parser delivers the interface for any parser object. Inherit from @@ -384,7 +396,7 @@ class parser(object): def parse(self, cr, data): ''' Parse data. - + data is a raw in memory file object. You have to split it in whatever chunks you see fit for parsing. It should return a list of mem_bank_statement objects. Every mem_bank_statement object @@ -400,7 +412,7 @@ class parser(object): be used as a prefix. Adding a tracer (day resolution) can create uniqueness. Adding unique statement ids can add to the robustness of your transaction numbering. - + Just mind that users can create random (file)containers with transactions in it. Try not to depend on order of appearance within these files. If in doubt: sort. @@ -408,5 +420,3 @@ class parser(object): raise NotImplementedError( _('This is a stub. Please implement your own.') ) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/record.py b/account_banking/record.py index 18486c020..54768e34b 100644 --- a/account_banking/record.py +++ b/account_banking/record.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -31,16 +31,19 @@ from datetime import datetime, date # Correct python2.4 issues try: datetime.strptime + def strpdate(str, format): return datetime.strptime(str, format).date() except AttributeError: import time + def strpdate(str, format): tm = time.strptime(str, format) return date(tm.tm_year, tm.tm_mon, tm.tm_mday) import unicodedata + class Field(object): '''Base Field class - fixed length left aligned string field in a record''' def __init__(self, name, length=1, fillchar=' ', cast=str): @@ -57,11 +60,14 @@ class Field(object): def take(self, buffer): offset = hasattr(self, 'offset') and self.offset or 0 - return self.cast(buffer[offset:offset + self.length].rstrip(self.fillchar)) + return self.cast(buffer[offset:offset + self.length].rstrip( + self.fillchar) + ) def __repr__(self): return '%s "%s"' % (self.__class__.__name__, self.name) + class Filler(Field): '''Constant value field''' def __init__(self, name, length=1, value=' '): @@ -73,9 +79,10 @@ class Filler(Field): def format(self, value): return super(Filler, self).format( - self.value * (self.length / len(self.value) +1) + self.value * (self.length / len(self.value) + 1) ) + class DateField(Field): '''Variable date field''' def __init__(self, name, format='%Y-%m-%d', auto=False, cast=str): @@ -98,6 +105,7 @@ class DateField(Field): return strpdate(value, self.dateformat) return self.auto and date.today() or None + class RightAlignedField(Field): '''Deviation of Field: right aligned''' def format(self, value): @@ -107,7 +115,10 @@ class RightAlignedField(Field): def take(self, buffer): offset = hasattr(self, 'offset') and self.offset or 0 - return self.cast(buffer[offset:offset + self.length].lstrip(self.fillchar)) + return self.cast(buffer[offset:offset + self.length].lstrip( + self.fillchar) + ) + class NumberField(RightAlignedField): '''Deviation of Field: left zero filled''' @@ -118,6 +129,7 @@ class NumberField(RightAlignedField): def format(self, value): return super(NumberField, self).format(self.cast(value or '')) + class RecordType(object): fields = [] @@ -130,7 +142,7 @@ class RecordType(object): offset += field.length def __len__(self): - return reduce(lambda x,y: x+y.length, self.fields, 0) + return reduce(lambda x, y: x + y.length, self.fields, 0) def __contains__(self, key): return any(lambda x, y=key: x.name == y, self.fields) @@ -139,7 +151,7 @@ class RecordType(object): for field in self.fields: if field.name == key: return field - raise KeyError, 'No such field: %s' % key + raise KeyError('No such field: %s' % key) def format(self, buffer): result = [] @@ -150,7 +162,8 @@ class RecordType(object): def take(self, buffer): return dict(zip([x.name for x in self.fields], [x.take(buffer) for x in self.fields] - )) + )) + class Record(object): _recordtype = None @@ -159,12 +172,12 @@ class Record(object): if hasattr(self, '_fields') and self._fields: self._recordtype = RecordType(self._fields) if not self._recordtype and not recordtype: - raise ValueError, 'No recordtype specified' + raise ValueError('No recordtype specified') if not self._recordtype: self._recordtype = recordtype() self._length = len(self._recordtype) self._value = value.ljust(self._length)[:self._length] - + def __len__(self): return self._length @@ -173,23 +186,24 @@ class Record(object): super(Record, self).__setattr__(attr, value) else: field = self._recordtype[attr] - self._value = self._value[:field.offset] + \ - field.format(value) + \ - self._value[field.offset + field.length:] + self._value = ( + self._value[:field.offset] + + field.format(value) + + self._value[field.offset + field.length:] + ) def __getattr__(self, attr): if attr.startswith('_'): return super(Record, self).__getattr__(attr) field = self._recordtype[attr] return field.take(self._value) - + def __str__(self): return self._recordtype.format(self._value) def __unicode__(self): return unicode(self.cast(self)) + def asciify(str): return unicodedata.normalize('NFKD', str).encode('ascii', 'ignore') - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/res_bank.py b/account_banking/res_bank.py index a73033242..0e858c31c 100644 --- a/account_banking/res_bank.py +++ b/account_banking/res_bank.py @@ -2,7 +2,7 @@ ############################################################################## # # Copyright 2011 - 2014 Therp BV (). -# +# # 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 @@ -25,7 +25,7 @@ class ResBank(orm.Model): def online_bank_info(self, cr, uid, bic, context=None): """ - API hook for legacy online lookup of BICs, + API hook for legacy online lookup of BICs, to be removed in OpenERP 8.0. """ return False, False diff --git a/account_banking/res_partner.py b/account_banking/res_partner.py index 4166b89d5..1e2970412 100644 --- a/account_banking/res_partner.py +++ b/account_banking/res_partner.py @@ -30,7 +30,7 @@ class ResPartner(orm.Model): self, cr, uid, ids, get_property_account, context=None): """ Returns the property journal account for the given partners ids. - + :param get_property_account: method of this object that takes a partner browse record and returns a field name of type many2one. """ diff --git a/account_banking/sepa/iban.py b/account_banking/sepa/iban.py index ece173404..58b1ddeb9 100644 --- a/account_banking/sepa/iban.py +++ b/account_banking/sepa/iban.py @@ -44,6 +44,7 @@ __all__ = ['IBAN', 'BBAN'] + def modulo_97_base10(abuffer): ''' Calculate the modulo 97 value of a string in base10 @@ -55,6 +56,7 @@ def modulo_97_base10(abuffer): checksum %= 97 return checksum + def base36_to_base10str(abuffer): ''' Convert a base36 string value to a string of base10 digits. @@ -67,6 +69,7 @@ def base36_to_base10str(abuffer): result += digit return result + class BBANFormat(object): ''' A BBANFormat is an auxilliary class for IBAN. It represents the composition @@ -80,7 +83,7 @@ class BBANFormat(object): Specify the structure of the SEPA account in relation to the local account. The XXZZ prefix that all SEPA accounts have is not part of the structure in BBANFormat. - + ibanfmt: string of identifiers from position 5 (start = 1): A = Account position N = Account digit @@ -95,7 +98,7 @@ class BBANFormat(object): leading-zero-stripped account numbers. Example: (NL) 'CCCCAAAAAAAAAA' - will convert 'INGB0001234567' into + will convert 'INGB0001234567' into bankcode 'INGB' and account '0001234567' bbanfmt: string of placeholders for the local bank account @@ -119,7 +122,7 @@ class BBANFormat(object): self._iban = ibanfmt self._bban = bbanfmt self._nolz = nolz - + def __extract__(self, spec, value): '''Extract the value based on the spec''' i = self._iban.find(spec) @@ -147,7 +150,7 @@ class BBANFormat(object): else: prefix = '' return prefix + self.__extract__('A', iban) - + def BBAN(self, iban): ''' Format the BBAN part of the IBAN in iban following the local @@ -178,6 +181,7 @@ class BBANFormat(object): i += 1 return res + class IBAN(str): ''' A IBAN string represents a SEPA bank account number. This class provides @@ -272,7 +276,7 @@ class IBAN(str): if item.isalnum(): init += item elif item not in ' \t.-': - raise ValueError, 'Invalid chars found in IBAN number' + raise ValueError('Invalid chars found in IBAN number') return str.__new__(cls, init) def __init__(self, *args, **kwargs): @@ -287,8 +291,7 @@ class IBAN(str): @classmethod def create(cls, BIC=None, countrycode=None, BBAN=None, bankcode=None, - branchcode=None, account=None - ): + branchcode=None, account=None): ''' Create a IBAN number from a BBAN and a country code. Optionaly create a BBAN from BBAN components before generation. @@ -304,20 +307,17 @@ class IBAN(str): if countrycode: countrycode = countrycode.upper() else: - raise ValueError, \ - 'Either BIC or countrycode is required' + raise ValueError('Either BIC or countrycode is required') if countrycode not in cls.countries: - raise ValueError, \ - '%s is not a SEPA country' % countrycode + raise ValueError('%s is not a SEPA country' % countrycode) format = cls.BBAN_formats[countrycode] if BBAN: if len(BBAN) == len(format._iban): ibanno = cls(countrycode + '00' + BBAN) return cls(countrycode + ibanno.checksum + BBAN) - raise ValueError, \ - 'Insufficient data to generate IBAN' + raise ValueError('Insufficient data to generate IBAN') @property def valid(self): @@ -325,8 +325,10 @@ class IBAN(str): Check if the string + check digits deliver a valid checksum ''' _buffer = self[4:] + self[:4] - return self.countrycode in self.countries and \ - int(base36_to_base10str(_buffer)) % 97 == 1 + return ( + self.countrycode in self.countries + and int(base36_to_base10str(_buffer)) % 97 == 1 + ) def __repr__(self): ''' @@ -387,7 +389,7 @@ class IBAN(str): The bank code seems to be world wide unique. Knowing this, one can use the country + bankcode info from BIC to narrow a search for the bank itself. - + Note that some countries use one single localization code for all bank transactions in that country, while others do not. This makes it impossible to use an algorithmic approach for generating @@ -421,22 +423,24 @@ class IBAN(str): ''' return self[4:] + class BBAN(object): ''' Class to reformat a local BBAN account number to IBAN specs. Simple validation based on length of spec string elements and real data. ''' - + @staticmethod def _get_length(fmt, element): ''' Internal method to calculate the length of a parameter in a formatted string ''' - i = 0; max_i = len(fmt._iban) + i = 0 + max_i = len(fmt._iban) while i < max_i: if fmt._iban[i] == element: - next = i +1 + next = i + 1 while next < max_i and fmt._iban[next] == element: next += 1 return next - i @@ -453,7 +457,10 @@ class BBAN(object): if countrycode.upper() in IBAN.countries: self._fmt = IBAN.BBAN_formats[countrycode.upper()] res = '' - i = 0; j = 0; max_i = len(self._fmt._bban); max_j = len(bban) + i = 0 + j = 0 + max_i = len(self._fmt._bban) + max_j = len(bban) while i < max_i and j < max_j: while bban[j] in ' \t' and j < max_j: j += 1 @@ -475,7 +482,7 @@ class BBAN(object): # Note that many accounts in the IBAN standard # are allowed to have leading zeros, so zfill # to full spec length for visual validation. - # + # # Note 2: this may look funny to some, as most # local schemes strip leading zeros. It allows # us however to present the user a visual feedback @@ -512,15 +519,16 @@ class BBAN(object): '''Simple check if BBAN is in the right format''' return self._bban and True or False + if __name__ == '__main__': import sys for arg in sys.argv[1:]: iban = IBAN(arg) - print 'IBAN:', iban - print 'country code:', iban.countrycode - print 'bank code:', iban.bankcode - print 'branch code:', iban.branchcode - print 'BBAN:', iban.BBAN - print 'localized BBAN:', iban.localized_BBAN - print 'check digits:', iban.checkdigits - print 'checksum:', iban.checksum + print('IBAN:', iban) + print('country code:', iban.countrycode) + print('bank code:', iban.bankcode) + print('branch code:', iban.branchcode) + print('BBAN:', iban.BBAN) + print('localized BBAN:', iban.localized_BBAN) + print('check digits:', iban.checkdigits) + print('checksum:', iban.checksum) diff --git a/account_banking/sepa/postalcode.py b/account_banking/sepa/postalcode.py index 1220c4ea4..c0b8bf81c 100644 --- a/account_banking/sepa/postalcode.py +++ b/account_banking/sepa/postalcode.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -27,6 +27,7 @@ import re __all__ = ['split', 'get', 'PostalCode'] + class PostalCode(object): ''' The PostalCode class is a wrapper around PostCodeFormat and an internal @@ -46,11 +47,11 @@ class PostalCode(object): ''' # Sort formats on length, longest first formats = [(len(x), x) for x in format.split('|')] - formats = [x[1] for x in sorted(formats, lambda x,y: -cmp(x,y))] - self.res = [re.compile(x.replace('#', '\\d').replace('@','[A-Z]')) + formats = [x[1] for x in sorted(formats, lambda x, y: -cmp(x, y))] + self.res = [re.compile(x.replace('#', '\\d').replace('@', '[A-Z]')) for x in formats - ] - + ] + def get(self, str_): ''' Return the postal code from the string str_ @@ -99,7 +100,8 @@ class PostalCode(object): 'IM': '@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA', 'IL': '#####', 'IT': '####', 'JM': '', 'JP': '###-####', 'JE': '@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA', - 'JO': '#####', 'KZ': '######', 'KE': '#####', 'KI': '', 'KP': '###-###', + 'JO': '#####', 'KZ': '######', 'KE': '#####', 'KI': '', + 'KP': '###-###', 'KR': 'SEOUL ###-###', 'KW': '#####', 'KG': '######', 'LA': '#####', 'LV': 'LV-####', 'LB': '#### ####|####', 'LS': '###', 'LR': '####', 'LY': '', 'LI': '####', 'LT': 'LT-#####', 'LU': '####', 'MO': '', @@ -139,7 +141,7 @@ class PostalCode(object): country . Returns iso, postal code and the remaining part of . - + When iso is filled but postal code remains empty, no postal code could be found according to the rules of iso. @@ -155,14 +157,12 @@ class PostalCode(object): # Find optimum (= max length postalcode) when iso code is unknown all = {} - opt_iso = '' max_l = 0 for key in cls._formats.iterkeys(): i, p, c = cls.split(str_, key) l = len(p) if l > max_l: max_l = l - opt_iso = i if l in all: all[l].append((i, p, c)) else: diff --git a/account_banking/struct.py b/account_banking/struct.py index e47d5faf3..b5e27e57d 100644 --- a/account_banking/struct.py +++ b/account_banking/struct.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -18,6 +18,7 @@ # along with this program. If not, see . # ############################################################################## + ''' Define a struct class which behaves like a dict, but allows using object.attr alongside object['attr']. @@ -25,6 +26,7 @@ object.attr alongside object['attr']. __all__ = ['struct'] + class struct(dict): ''' Ease working with dicts. Allow dict.key alongside dict['key'] @@ -52,4 +54,4 @@ class struct(dict): else: fmt = '%*.*s%%s: %%s' % (indent, indent, '') for item in self.iteritems(): - print fmt % item + print(fmt % item) diff --git a/account_banking/wizard/__init__.py b/account_banking/wizard/__init__.py index 169e55e51..e2ed25a0f 100644 --- a/account_banking/wizard/__init__.py +++ b/account_banking/wizard/__init__.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -18,8 +18,7 @@ # along with this program. If not, see . # ############################################################################## -import bank_import -import banking_transaction_wizard -import link_partner -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: +from . import bank_import +from . import banking_transaction_wizard +from . import link_partner diff --git a/account_banking/wizard/bank_import.py b/account_banking/wizard/bank_import.py index c5caff7c5..5b73eaf72 100644 --- a/account_banking/wizard/bank_import.py +++ b/account_banking/wizard/bank_import.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -23,11 +23,13 @@ # Kaspars Vilkens (KNdati): lenghty discussions, bugreports and bugfixes # Stefan Rijnhart (Therp): bugreport and bugfix # + ''' This module contains the business logic of the wizard account_banking_import. The parsing is done in the parser modules. Every parser module is required to use parser.models as a mean of communication with the business logic. ''' + import base64 import datetime from openerp.osv import orm, fields @@ -44,6 +46,7 @@ bt = models.mem_bank_transaction # the real payment date. This can occur with online transactions (web shops). payment_window = datetime.timedelta(days=10) + def parser_types(*args, **kwargs): '''Delay evaluation of parser types until start of wizard, to allow depending modules to initialize and add their parsers to the list @@ -57,18 +60,26 @@ class banking_import_line(orm.TransientModel): _columns = { 'name': fields.char('Name', size=64), 'date': fields.date('Date', readonly=True), - 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')), + 'amount': fields.float( + 'Amount', + digits_compute=dp.get_precision('Account'), + ), 'statement_line_id': fields.many2one( 'account.bank.statement.line', 'Resulting statement line', readonly=True), 'type': fields.selection([ - ('supplier','Supplier'), - ('customer','Customer'), - ('general','General') + ('supplier', 'Supplier'), + ('customer', 'Customer'), + ('general', 'General') ], 'Type', required=True), 'partner_id': fields.many2one('res.partner', 'Partner'), - 'statement_id': fields.many2one('account.bank.statement', 'Statement', - select=True, required=True, ondelete='cascade'), + 'statement_id': fields.many2one( + 'account.bank.statement', + 'Statement', + select=True, + required=True, + ondelete='cascade', + ), 'ref': fields.char('Reference', size=32), 'note': fields.text('Notes'), 'period_id': fields.many2one('account.period', 'Period'), @@ -83,16 +94,19 @@ class banking_import_line(orm.TransientModel): 'account.invoice', 'banking_import_line_invoice_rel', 'line_id', 'invoice_id'), 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account'), - 'transaction_type': fields.selection([ + 'transaction_type': fields.selection( + [ # TODO: payment terminal etc... ('invoice', 'Invoice payment'), ('storno', 'Canceled debit order'), ('bank_costs', 'Bank costs'), ('unknown', 'Unknown'), - ], 'Transaction type'), + ], + 'Transaction type', + ), 'duplicate': fields.boolean('Duplicate'), } - + class banking_import(orm.TransientModel): _name = 'account.banking.bank.import' @@ -119,7 +133,8 @@ class banking_import(orm.TransientModel): if not parser: raise orm.except_orm( _('ERROR!'), - _('Unable to import parser %(parser)s. Parser class not found.') % + _('Unable to import parser %(parser)s. Parser class not ' + 'found.') % {'parser': parser_code} ) @@ -133,16 +148,17 @@ class banking_import(orm.TransientModel): if any([x for x in statements if not x.is_valid()]): raise orm.except_orm( _('ERROR!'), - _('The imported statements appear to be invalid! Check your file.') + _('The imported statements appear to be invalid! Check your ' + 'file.') ) # Create the file now, as the statements need to be linked to it import_id = statement_file_obj.create(cr, uid, dict( - company_id = company.id, - file = statements_file, - file_name = banking_import.file_name, - state = 'unfinished', - format = parser.name, + company_id=company.id, + file=statements_file, + file_name=banking_import.file_name, + state='unfinished', + format=parser.name, )) bank_country_code = False @@ -151,14 +167,14 @@ class banking_import(orm.TransientModel): # Results results = struct( - stat_loaded_cnt = 0, - trans_loaded_cnt = 0, - stat_skipped_cnt = 0, - trans_skipped_cnt = 0, - trans_matched_cnt = 0, - bank_costs_invoice_cnt = 0, - error_cnt = 0, - log = [], + stat_loaded_cnt=0, + trans_loaded_cnt=0, + stat_skipped_cnt=0, + trans_skipped_cnt=0, + trans_matched_cnt=0, + bank_costs_invoice_cnt=0, + error_cnt=0, + log=[], ) # Caching @@ -175,7 +191,9 @@ class banking_import(orm.TransientModel): continue # Create fallback currency code - currency_code = statement.local_currency or company.currency_id.name + currency_code = ( + statement.local_currency or company.currency_id.name + ) # Check cache for account info/currency if statement.local_account in info and \ @@ -190,8 +208,10 @@ class banking_import(orm.TransientModel): ) if not account_info: results.log.append( - _('Statements found for unknown account %(bank_account)s') % - {'bank_account': statement.local_account} + _('Statements found for unknown account ' + '%(bank_account)s') % { + 'bank_account': statement.local_account + } ) error_accounts[statement.local_account] = True results.error_cnt += 1 @@ -200,7 +220,7 @@ class banking_import(orm.TransientModel): results.log.append( _('Statements found for account %(bank_account)s, ' 'but no default journal was defined.' - ) % {'bank_account': statement.local_account} + ) % {'bank_account': statement.local_account} ) error_accounts[statement.local_account] = True results.error_cnt += 1 @@ -210,7 +230,7 @@ class banking_import(orm.TransientModel): currency_code = account_info.currency_id.name # Cache results - if not statement.local_account in info: + if statement.local_account not in info: info[statement.local_account] = { currency_code: account_info } @@ -222,22 +242,22 @@ class banking_import(orm.TransientModel): and account_info.currency_id.name != statement.local_currency: # TODO: convert currencies? results.log.append( - _('Statement %(statement_id)s for account %(bank_account)s' + _('Statement %(statement_id)s for account %(bank_account)s' ' uses different currency than the defined bank journal.' - ) % { - 'bank_account': statement.local_account, - 'statement_id': statement.id - } + ) % { + 'bank_account': statement.local_account, + 'statement_id': statement.id + } ) error_accounts[statement.local_account] = True results.error_cnt += 1 continue # Check existence of previous statement - # Less well defined formats can resort to a + # Less well defined formats can resort to a # dynamically generated statement identification # (e.g. a datetime string of the moment of import) - # and have potential duplicates flagged by the + # and have potential duplicates flagged by the # matching procedure statement_ids = statement_obj.search(cr, uid, [ ('name', '=', statement.id), @@ -251,7 +271,8 @@ class banking_import(orm.TransientModel): ) continue - # Get the period for the statement (as bank statement object checks this) + # Get the period for the statement (as bank statement object + # checks this) period_ids = period_obj.search( cr, uid, [ ('company_id', '=', company.id), @@ -259,7 +280,7 @@ class banking_import(orm.TransientModel): ('date_stop', '>=', statement.date), ('special', '=', False), ], context=context) - + if not period_ids: results.log.append( _('No period found covering statement date %(date)s, ' @@ -272,17 +293,17 @@ class banking_import(orm.TransientModel): # Create the bank statement record statement_id = statement_obj.create(cr, uid, dict( - name = statement.id, - journal_id = account_info.journal_id.id, - date = convert.date2str(statement.date), - balance_start = statement.start_balance, - balance_end_real = statement.end_balance, - balance_end = statement.end_balance, - state = 'draft', - user_id = uid, - banking_id = import_id, - company_id = company.id, - period_id = period_ids[0], + name=statement.id, + journal_id=account_info.journal_id.id, + date=convert.date2str(statement.date), + balance_start=statement.start_balance, + balance_end_real=statement.end_balance, + balance_end=statement.end_balance, + state='draft', + user_id=uid, + banking_id=import_id, + company_id=company.id, + period_id=period_ids[0], )) imported_statement_ids.append(statement_id) @@ -294,7 +315,8 @@ class banking_import(orm.TransientModel): values = {} for attr in transaction.__slots__ + ['type']: if attr in import_transaction_obj.column_map: - values[import_transaction_obj.column_map[attr]] = eval('transaction.%s' % attr) + values[import_transaction_obj.column_map[attr]] = \ + eval('transaction.%s' % attr) elif attr in import_transaction_obj._columns: values[attr] = eval('transaction.%s' % attr) values['statement_id'] = statement_id @@ -305,35 +327,17 @@ class banking_import(orm.TransientModel): transaction_id = import_transaction_obj.create( cr, uid, values, context=context) transaction_ids.append(transaction_id) - + results.stat_loaded_cnt += 1 - import_transaction_obj.match(cr, uid, transaction_ids, results=results, context=context) - - #recompute statement end_balance for validation + import_transaction_obj.match( + cr, uid, transaction_ids, results=results, context=context + ) + + # recompute statement end_balance for validation statement_obj.button_dummy( cr, uid, imported_statement_ids, context=context) - - # Original code. Didn't take workflow logistics into account... - # - #cr.execute( - # "UPDATE payment_order o " - # "SET state = 'done', " - # "date_done = '%s' " - # "FROM payment_line l " - # "WHERE o.state = 'sent' " - # "AND o.id = l.order_id " - # "AND l.id NOT IN (" - # "SELECT DISTINCT id FROM payment_line " - # "WHERE date_done IS NULL " - # "AND id IN (%s)" - # ")" % ( - # time.strftime('%Y-%m-%d'), - # ','.join([str(x) for x in payment_line_ids]) - # ) - #) - report = [ '%s: %s' % (_('Total number of statements'), results.stat_skipped_cnt + results.stat_loaded_cnt), @@ -360,15 +364,18 @@ class banking_import(orm.TransientModel): text_log = '\n'.join(report + results.log) state = results.error_cnt and 'error' or 'ready' statement_file_obj.write(cr, uid, import_id, dict( - state = state, log = text_log, - ), context) + state=state, + log=text_log, + ), context) if not imported_statement_ids or not results.trans_loaded_cnt: # file state can be 'ready' while import state is 'error' state = 'error' self.write(cr, uid, [ids[0]], dict( - import_id = import_id, log = text_log, state = state, - statement_ids = [(6, 0, imported_statement_ids)], - ), context) + import_id=import_id, + log=text_log, + state=state, + statement_ids=[(6, 0, imported_statement_ids)], + ), context) return { 'name': (state == 'ready' and _('Review Bank Statements') or _('Error')), @@ -393,13 +400,15 @@ class banking_import(orm.TransientModel): ), 'file_name': fields.char('File name', size=256), 'file': fields.binary( - 'Statements File', required=True, - help = ('The Transactions File to import. Please note that while it is ' - 'perfectly safe to reload the same file multiple times or to load in ' - 'timeframe overlapping statements files, there are formats that may ' - 'introduce different sequencing, which may create double entries.\n\n' - 'To stay on the safe side, always load bank statements files using the ' - 'same format.'), + 'Statements File', + required=True, + help=('The Transactions File to import. Please note that while it ' + 'is perfectly safe to reload the same file multiple times ' + 'or to load in timeframe overlapping statements files, ' + 'there are formats that may introduce different ' + 'sequencing, which may create double entries.\n\n' + 'To stay on the safe side, always load bank statements ' + 'files using the same format.'), states={ 'ready': [('readonly', True)], 'error': [('readonly', True)], @@ -435,8 +444,8 @@ class banking_import(orm.TransientModel): _defaults = { 'state': 'init', - 'company': lambda s,cr,uid,c: + 'company': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get( - cr, uid, 'bank.import.transaction', context=c), + cr, uid, 'bank.import.transaction', context=c), 'parser': _default_parser_type, } diff --git a/account_banking/wizard/banking_transaction_wizard.py b/account_banking/wizard/banking_transaction_wizard.py index 0941f88b2..dbcc7b886 100644 --- a/account_banking/wizard/banking_transaction_wizard.py +++ b/account_banking/wizard/banking_transaction_wizard.py @@ -7,8 +7,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -50,7 +50,7 @@ class banking_transaction_wizard(orm.TransientModel): return res def create_act_window(self, cr, uid, ids, nodestroy=True, context=None): - """ + """ Return a popup window for this model """ if isinstance(ids, (int, long)): @@ -78,10 +78,10 @@ class banking_transaction_wizard(orm.TransientModel): import_transaction_obj = self.pool.get('banking.import.transaction') trans_id = self.read( cr, uid, ids[0], ['import_transaction_id'], - context=context)['import_transaction_id'][0] # many2one tuple + context=context)['import_transaction_id'][0] # many2one tuple import_transaction_obj.match(cr, uid, [trans_id], context=context) return self.create_act_window(cr, uid, ids, context=None) - + def write(self, cr, uid, ids, vals, context=None): """ Implement a trigger to retrieve the corresponding move line @@ -121,22 +121,27 @@ class banking_transaction_wizard(orm.TransientModel): # Given the arity of the relation, there is are always # multiple possibilities but the move lines here are # prefiltered for having account_id.type payable/receivable - # and the regular invoice workflow should only come up with + # and the regular invoice workflow should only come up with # one of those only. for move_line in wiz.import_transaction_id.move_line_ids: if (move_line.invoice == wiz.import_transaction_id.invoice_id): transaction_obj.write( cr, uid, wiz.import_transaction_id.id, - { 'move_line_id': move_line.id, }, context=context) + {'move_line_id': move_line.id, }, + context=context + ) statement_line_obj.write( - cr, uid, wiz.import_transaction_id.statement_line_id.id, - { 'partner_id': move_line.partner_id.id or False, - 'account_id': move_line.account_id.id, - }, context=context) + cr, uid, + wiz.import_transaction_id.statement_line_id.id, + { + 'partner_id': ( + move_line.partner_id.id or False), + 'account_id': move_line.account_id.id, + }, context=context) found = True break - # Cannot match the invoice + # Cannot match the invoice if not found: orm.except_orm( _("No entry found for the selected invoice"), @@ -150,15 +155,16 @@ class banking_transaction_wizard(orm.TransientModel): # Rewrite *2many directive notation if manual_invoice_ids: manual_invoice_ids = ( - [i[1] for i in manual_invoice_ids if i[0]==4] + - [j for i in manual_invoice_ids if i[0]==6 for j in i[2]]) + [i[1] for i in manual_invoice_ids if i[0] == 4] + + [j for i in manual_invoice_ids if i[0] == 6 for j in i[2]]) if manual_move_line_ids: manual_move_line_ids = ( - [i[1] for i in manual_move_line_ids if i[0]==4] + - [j for i in manual_move_line_ids if i[0]==6 for j in i[2]]) + [i[1] for i in manual_move_line_ids if i[0] == 4] + + [j for i in manual_move_line_ids + if i[0] == 6 for j in i[2]]) for wiz in self.browse(cr, uid, ids, context=context): - #write can be called multiple times for the same values - #that doesn't hurt above, but it does here + # write can be called multiple times for the same values + # that doesn't hurt above, but it does here if wiz.match_type and ( len(manual_move_line_ids) > 1 or len(manual_invoice_ids) > 1): @@ -171,7 +177,8 @@ class banking_transaction_wizard(orm.TransientModel): found_move_line = False if invoice.move_id: for line in invoice.move_id.line_id: - if line.account_id.type in ('receivable', 'payable'): + if line.account_id.type in ('receivable', + 'payable'): todo.append((invoice.id, line.id)) found_move_line = True break @@ -181,12 +188,13 @@ class banking_transaction_wizard(orm.TransientModel): _("No entry found for the selected invoice. ")) for move_line_id in manual_move_line_ids: todo_entry = [False, move_line_id] - move_line=move_line_obj.read( - cr, - uid, - move_line_id, - ['invoice'], - context=context) + move_line = move_line_obj.read( + cr, + uid, + move_line_id, + ['invoice'], + context=context + ) if move_line['invoice']: todo_entry[0] = move_line['invoice'][0] todo.append(todo_entry) @@ -194,25 +202,27 @@ class banking_transaction_wizard(orm.TransientModel): while todo: todo_entry = todo.pop() move_line = move_line_obj.browse( - cr, uid, todo_entry[1], context) + cr, uid, todo_entry[1], context) transaction_id = wiz.import_transaction_id.id statement_line_id = wiz.statement_line_id.id if len(todo) > 0: statement_line_id = wiz.statement_line_id.split_off( - move_line.debit or -move_line.credit)[0] + move_line.debit or -move_line.credit)[0] transaction_id = statement_line_obj.browse( - cr, - uid, - statement_line_id, - context=context).import_transaction_id.id + cr, + uid, + statement_line_id, + context=context + ).import_transaction_id.id vals = { 'move_line_id': todo_entry[1], 'move_line_ids': [(6, 0, [todo_entry[1]])], 'invoice_id': todo_entry[0], - 'invoice_ids': [(6, 0, - [todo_entry[0]] if todo_entry[0] else [])], + 'invoice_ids': [ + (6, 0, [todo_entry[0]] if todo_entry[0] else []) + ], 'match_type': 'manual', } @@ -221,7 +231,7 @@ class banking_transaction_wizard(orm.TransientModel): st_line_vals = { 'account_id': move_line_obj.read( - cr, uid, todo_entry[1], + cr, uid, todo_entry[1], ['account_id'], context=context)['account_id'][0], } @@ -231,7 +241,7 @@ class banking_transaction_wizard(orm.TransientModel): ).partner_id.commercial_partner_id.id statement_line_obj.write( - cr, uid, statement_line_id, + cr, uid, statement_line_id, st_line_vals, context=context) return res @@ -255,22 +265,19 @@ class banking_transaction_wizard(orm.TransientModel): # Get the bank account setting record, to reset the account account_id = False journal_id = wiz.statement_line_id.statement_id.journal_id.id - setting_ids = settings_pool.find(cr, uid, journal_id, context=context) + setting_ids = settings_pool.find( + cr, uid, journal_id, context=context + ) # Restore partner id from the bank account or else reset partner_id = False if (wiz.statement_line_id.partner_bank_id and wiz.statement_line_id.partner_bank_id.partner_id): - partner_id = wiz.statement_line_id.partner_bank_id.partner_id.id + partner_id = ( + wiz.statement_line_id.partner_bank_id.partner_id.id + ) wiz.write({'partner_id': partner_id}) - # Select account type by parter customer or supplier, - # or default based on amount sign - if wiz.amount < 0: - account_type = 'payable' - else: - account_type = 'receivable' - bank_partner = False if partner_id: bank_partner = wiz.statement_line_id.partner_bank_id.partner_id @@ -295,13 +302,18 @@ class banking_transaction_wizard(orm.TransientModel): wiz.statement_line_id.write({'account_id': account_id}) if wiz.statement_line_id: - #delete splits causing an unsplit if this is a split - #transaction - statement_pool.unlink(cr, uid, - statement_pool.search(cr, uid, - [('parent_id', '=', wiz.statement_line_id.id)], - context=context), - context=context) + # delete splits causing an unsplit if this is a split + # transaction + statement_pool.unlink( + cr, + uid, + statement_pool.search( + cr, uid, + [('parent_id', '=', wiz.statement_line_id.id)], + context=context + ), + context=context + ) if wiz.import_transaction_id: wiz.import_transaction_id.clear_and_write() @@ -313,15 +325,15 @@ class banking_transaction_wizard(orm.TransientModel): ids = [ids] transaction_obj = self.pool.get('banking.import.transaction') for wiz in self.read( - cr, uid, ids, ['duplicate', 'import_transaction_id'], - context=context): + cr, uid, ids, ['duplicate', 'import_transaction_id'], + context=context): transaction_obj.write( - cr, uid, wiz['import_transaction_id'][0], + cr, uid, wiz['import_transaction_id'][0], {'duplicate': not wiz['duplicate']}, context=context) return self.create_act_window(cr, uid, ids, context=None) def button_done(self, cr, uid, ids, context=None): - return {'type': 'ir.actions.act_window_close'} + return {'type': 'ir.actions.act_window_close'} _columns = { 'name': fields.char('Name', size=64), @@ -349,22 +361,26 @@ class banking_transaction_wizard(orm.TransientModel): 'statement_line_id', 'parent_id', type='many2one', relation='account.bank.statement.line', readonly=True), 'import_transaction_id': fields.related( - 'statement_line_id', 'import_transaction_id', + 'statement_line_id', 'import_transaction_id', string="Import transaction", type='many2one', relation='banking.import.transaction'), 'residual': fields.related( - 'import_transaction_id', 'residual', type='float', + 'import_transaction_id', 'residual', type='float', string='Residual', readonly=True), 'writeoff_account_id': fields.related( 'import_transaction_id', 'writeoff_account_id', type='many2one', relation='account.account', string='Write-off account'), 'invoice_ids': fields.related( - 'import_transaction_id', 'invoice_ids', string="Matching invoices", + 'import_transaction_id', 'invoice_ids', string="Matching invoices", type='many2many', relation='account.invoice'), 'invoice_id': fields.related( - 'import_transaction_id', 'invoice_id', string="Invoice to reconcile", - type='many2one', relation='account.invoice'), + 'import_transaction_id', + 'invoice_id', + string="Invoice to reconcile", + type='many2one', + relation='account.invoice', + ), 'move_line_ids': fields.related( 'import_transaction_id', 'move_line_ids', string="Entry lines", type='many2many', relation='account.move.line'), @@ -372,15 +388,20 @@ class banking_transaction_wizard(orm.TransientModel): 'import_transaction_id', 'move_line_id', string="Entry line", type='many2one', relation='account.move.line'), 'duplicate': fields.related( - 'import_transaction_id', 'duplicate', string='Flagged as duplicate', - type='boolean'), + 'import_transaction_id', + 'duplicate', + string='Flagged as duplicate', + type='boolean', + ), 'match_multi': fields.related( - 'import_transaction_id', 'match_multi', + 'import_transaction_id', 'match_multi', type="boolean", string='Multiple matches'), 'match_type': fields.related( - 'import_transaction_id', 'match_type', type='selection', + 'import_transaction_id', + 'match_type', + type='selection', selection=[ - ('move','Move'), + ('move', 'Move'), ('invoice', 'Invoice'), ('payment', 'Payment line'), ('payment_order', 'Payment order'), @@ -388,8 +409,10 @@ class banking_transaction_wizard(orm.TransientModel): ('manual', 'Manual'), ('payment_manual', 'Payment line (manual)'), ('payment_order_manual', 'Payment order (manual)'), - ], - string='Match type', readonly=True), + ], + string='Match type', + readonly=True, + ), 'manual_invoice_ids': fields.many2many( 'account.invoice', 'banking_transaction_wizard_account_invoice_rel', @@ -401,8 +424,17 @@ class banking_transaction_wizard(orm.TransientModel): 'wizard_id', 'move_line_id', string='Or match one or more entries', domain=[('account_id.reconcile', '=', True), ('reconcile_id', '=', False)]), - 'payment_option': fields.related('import_transaction_id','payment_option', string='Payment Difference', type='selection', required=True, - selection=[('without_writeoff', 'Keep Open'),('with_writeoff', 'Reconcile Payment Balance')]), + 'payment_option': fields.related( + 'import_transaction_id', + 'payment_option', + string='Payment Difference', + type='selection', + required=True, + selection=[ + ('without_writeoff', 'Keep Open'), + ('with_writeoff', 'Reconcile Payment Balance') + ], + ), 'writeoff_analytic_id': fields.related( 'import_transaction_id', 'writeoff_analytic_id', type='many2one', relation='account.analytic.account', @@ -411,9 +443,11 @@ class banking_transaction_wizard(orm.TransientModel): 'statement_line_id', 'analytic_account_id', type='many2one', relation='account.analytic.account', string="Analytic Account"), - 'move_currency_amount': fields.related('import_transaction_id','move_currency_amount', - type='float', string='Match Currency Amount', readonly=True), - } - -banking_transaction_wizard() - + 'move_currency_amount': fields.related( + 'import_transaction_id', + 'move_currency_amount', + type='float', + string='Match Currency Amount', + readonly=True, + ), + } diff --git a/account_banking/wizard/banktools.py b/account_banking/wizard/banktools.py index 1fae20cfe..09f735689 100644 --- a/account_banking/wizard/banktools.py +++ b/account_banking/wizard/banktools.py @@ -5,8 +5,8 @@ # All Rights Reserved # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -24,7 +24,7 @@ from openerp.addons.account_banking import sepa from openerp.addons.account_banking.struct import struct __all__ = [ - 'get_period', + 'get_period', 'get_bank_accounts', 'get_partner', 'get_country_id', @@ -32,6 +32,7 @@ __all__ = [ 'create_bank_account', ] + def get_period(pool, cr, uid, date, company, log=None): ''' Wrapper over account_period.find() to log exceptions of @@ -43,7 +44,7 @@ def get_period(pool, cr, uid, date, company, log=None): try: period_ids = pool.get('account.period').find( cr, uid, dt=date, context=context) - except Exception, e: + except Exception as e: if log is None: raise else: @@ -51,6 +52,7 @@ def get_period(pool, cr, uid, date, company, log=None): return False return period_ids[0] + def get_bank_accounts(pool, cr, uid, account_number, log, fail=False): ''' Get the bank account with account number account_number @@ -72,6 +74,7 @@ def get_bank_accounts(pool, cr, uid, account_number, log, fail=False): return [] return partner_bank_obj.browse(cr, uid, bank_account_ids) + def _has_attr(obj, attr): # Needed for dangling addresses and a weird exception scheme in # OpenERP's orm. @@ -80,6 +83,7 @@ def _has_attr(obj, attr): except KeyError: return False + def get_partner(pool, cr, uid, name, address, postal_code, city, country_id, log, context=None): ''' @@ -87,7 +91,7 @@ def get_partner(pool, cr, uid, name, address, postal_code, city, If multiple partners are found with the same name, select the first and add a warning to the import log. - + TODO: revive the search by lines from the address argument ''' partner_obj = pool.get('res.partner') @@ -115,7 +119,8 @@ def get_partner(pool, cr, uid, name, address, postal_code, city, key = name.lower() partners = [] for partner in partner_obj.read( - cr, uid, partner_search_ids, ['name', 'commercial_partner_id'], context=context): + cr, uid, partner_search_ids, ['name', 'commercial_partner_id'], + context=context): if (len(partner['name']) > 3 and partner['name'].lower() in key): partners.append(partner) partners.sort(key=lambda x: len(x['name']), reverse=True) @@ -126,6 +131,7 @@ def get_partner(pool, cr, uid, name, address, postal_code, city, 'name %(name)s') % {'name': name}) return partner_ids and partner_ids[0] or False + def get_company_bank_account(pool, cr, uid, account_number, currency, company, log): ''' @@ -139,16 +145,16 @@ def get_company_bank_account(pool, cr, uid, account_number, currency, return False elif len(bank_accounts) != 1: log.append( - _('More than one bank account was found with the same number %(account_no)s') - % dict(account_no = account_number) + _('More than one bank account was found with the same number ' + '%(account_no)s') % dict(account_no=account_number) ) return False if bank_accounts[0].partner_id.id != company.partner_id.id: log.append( _('Account %(account_no)s is not owned by %(partner)s') - % dict(account_no = account_number, - partner = company.partner_id.name, - )) + % dict(account_no=account_number, + partner=company.partner_id.name, + )) return False results.account = bank_accounts[0] bank_settings_obj = pool.get('account.banking.account.settings') @@ -189,8 +195,9 @@ def get_company_bank_account(pool, cr, uid, account_number, currency, return results + def get_or_create_bank(pool, cr, uid, bic, online=False, code=None, - name=None): + name=None, context=None): ''' Find or create the bank with the provided BIC code. When online, the SWIFT database will be consulted in order to @@ -231,38 +238,41 @@ def get_or_create_bank(pool, cr, uid, bic, online=False, code=None, bank_id = False if online: - info, address = bank_obj.online_bank_info(cr, uid, bic, context=context) + info, address = bank_obj.online_bank_info( + cr, uid, bic, context=context + ) if info: bank_id = bank_obj.create(cr, uid, dict( - code = info.code, - name = info.name, - street = address.street, - street2 = address.street2, - zip = address.zip, - city = address.city, - country = country_id, - bic = info.bic[:8], + code=info.code, + name=info.name, + street=address.street, + street2=address.street2, + zip=address.zip, + city=address.city, + country=country_id, + bic=info.bic[:8], )) else: info = struct(name=name, code=code) if not online or not bank_id: bank_id = bank_obj.create(cr, uid, dict( - code = info.code or 'UNKNOW', - name = info.name or _('Unknown Bank'), - country = country_id, - bic = bic, + code=info.code or 'UNKNOW', # FIXME: Typo? + name=info.name or _('Unknown Bank'), + country=country_id, + bic=bic, )) return bank_id, country_id + def get_country_id(pool, cr, uid, transaction, context=None): """ Derive a country id from the info on the transaction. - + :param transaction: browse record of a transaction - :returns: res.country id or False + :returns: res.country id or False """ - + country_code = False iban = sepa.IBAN(transaction.remote_account) if iban.valid: @@ -283,6 +293,7 @@ def get_country_id(pool, cr, uid, transaction, context=None): country_id = company.partner_id.country.id return country_id + def create_bank_account(pool, cr, uid, partner_id, account_number, holder_name, address, city, country_id, bic=False, @@ -291,9 +302,9 @@ def create_bank_account(pool, cr, uid, partner_id, Create a matching bank account with this holder for this partner. ''' values = struct( - partner_id = partner_id, - owner_name = holder_name, - country_id = country_id, + partner_id=partner_id, + owner_name=holder_name, + country_id=country_id, ) # Are we dealing with IBAN? @@ -325,5 +336,3 @@ def create_bank_account(pool, cr, uid, partner_id, # Create bank account and return return pool.get('res.partner.bank').create( cr, uid, values, context=context) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/wizard/link_partner.py b/account_banking/wizard/link_partner.py index 3a74bd6c2..391abb30c 100644 --- a/account_banking/wizard/link_partner.py +++ b/account_banking/wizard/link_partner.py @@ -24,6 +24,7 @@ from openerp.tools.translate import _ from openerp.addons.account_banking.wizard import banktools import ast + class link_partner(orm.TransientModel): _name = 'banking.link_partner' _description = 'Link partner' @@ -66,14 +67,14 @@ class link_partner(orm.TransientModel): 'mobile': fields.char('Mobile', size=64), 'is_company': fields.boolean('Is a Company'), } - + _defaults = { 'is_company': True, } - + def create(self, cr, uid, vals, context=None): """ - Get default values from the transaction data + Get default values from the transaction data on the statement line """ if vals and vals.get('statement_line_id'): @@ -86,7 +87,7 @@ class link_partner(orm.TransientModel): raise orm.except_orm( _('Error'), _('Statement line is already linked to a bank account ')) - + if not(transaction and transaction.remote_account): raise orm.except_orm( @@ -124,17 +125,17 @@ class link_partner(orm.TransientModel): return super(link_partner, self).create( cr, uid, vals, context=context) - + def update_partner_values(self, cr, uid, wizard, values, context=None): """ Updates the new partner values with the values from the wizard - + :param wizard: read record of wizard (with load='_classic_write') :param values: the dictionary of partner values that will be updated """ for field in ['is_company', 'name', - 'street', + 'street', 'street2', 'zip', 'city', @@ -148,7 +149,7 @@ class link_partner(orm.TransientModel): if wizard[field]: values[field] = wizard[field] return True - + def link_partner(self, cr, uid, ids, context=None): statement_line_obj = self.pool.get( 'account.bank.statement.line') @@ -160,13 +161,13 @@ class link_partner(orm.TransientModel): wiz_read = self.read( cr, uid, ids[0], context=context, load='_classic_write') partner_vals = { - 'type': 'default', - } + 'type': 'default', + } self.update_partner_values( cr, uid, wiz_read, partner_vals, context=context) partner_id = self.pool.get('res.partner').create( cr, uid, partner_vals, context=context) - + partner_bank_id = banktools.create_bank_account( self.pool, cr, uid, partner_id, wiz.remote_account, wiz.name, @@ -185,10 +186,10 @@ class link_partner(orm.TransientModel): {'partner_bank_id': partner_bank_id, 'partner_id': partner_id}, context=context) - return {'type': 'ir.actions.act_window_close'} + return {'type': 'ir.actions.act_window_close'} def create_act_window(self, cr, uid, ids, nodestroy=True, context=None): - """ + """ Return a popup window for this model """ if isinstance(ids, (int, long)): @@ -205,5 +206,3 @@ class link_partner(orm.TransientModel): 'res_id': ids[0], 'nodestroy': nodestroy, } - -