diff --git a/account_banking/account_banking.py b/account_banking/account_banking.py index 8a6dd055f..14d9cc807 100644 --- a/account_banking/account_banking.py +++ b/account_banking/account_banking.py @@ -267,7 +267,7 @@ class account_banking_imported_file(orm.Model): } _defaults = { 'date': fields.date.context_today, - 'user_id': lambda self, cursor, uid, context: uid, + 'user_id': lambda self, cr, uid, context: uid, } account_banking_imported_file() @@ -320,12 +320,12 @@ class account_bank_statement(orm.Model): ['journal_id','period_id']), ] - def _get_period(self, cursor, uid, date, context=None): + def _get_period(self, cr, uid, date, context=None): ''' Find matching period for date, not meant for _defaults. ''' period_obj = self.pool.get('account.period') - periods = period_obj.find(cursor, uid, dt=date, context=context) + periods = period_obj.find(cr, uid, dt=date, context=context) return periods and periods[0] or False def _prepare_move( @@ -398,7 +398,7 @@ class account_bank_statement(orm.Model): # 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_obj.search( + torec = account_move_line_obj.search( cr, uid, [ ('move_id', 'in', move_ids), ('account_id', '=', st_line.account_id.id)], @@ -450,7 +450,7 @@ class account_voucher(orm.Model): context = {} if not context.get('period_id') and context.get('move_line_ids'): return self.pool.get('account.move.line').browse( - cr, uid , context.get('move_line_ids'))[0].period_id.id + cr, uid , context.get('move_line_ids'), context=context)[0].period_id.id return super(account_voucher, self)._get_period(cr, uid, context) account_voucher() @@ -467,12 +467,12 @@ class account_bank_statement_line(orm.Model): _inherit = 'account.bank.statement.line' _description = 'Bank Transaction' - def _get_period(self, cursor, user, context=None): + def _get_period(self, cr, uid, context=None): date = context.get('date', None) - periods = self.pool.get('account.period').find(cursor, user, dt=date) + periods = self.pool.get('account.period').find(cr, uid, dt=date) return periods and periods[0] or False - def _get_currency(self, cursor, user, context=None): + def _get_currency(self, cr, uid, context=None): ''' Get the default currency (required to allow other modules to function, which assume currency to be a calculated field and thus optional) @@ -480,7 +480,7 @@ 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(cursor, user, user, + 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): @@ -605,7 +605,7 @@ class res_partner_bank(orm.Model): iban = sepa.IBAN(acc_number) return (str(iban), iban.localized_BBAN) - def create(self, cursor, uid, vals, context=None): + def create(self, cr, uid, vals, context=None): ''' Create dual function IBAN account for SEPA countries ''' @@ -614,7 +614,7 @@ class res_partner_bank(orm.Model): or vals.get('acc_number_domestic', False)) vals['acc_number'], vals['acc_number_domestic'] = ( self._correct_IBAN(iban)) - return self._founder.create(self, cursor, uid, vals, context) + return self._founder.create(self, cr, uid, vals, context) def write(self, cr, uid, ids, vals, context=None): ''' @@ -637,7 +637,7 @@ class res_partner_bank(orm.Model): self._founder.write(self, cr, uid, account['id'], vals, context) return True - def search(self, cursor, uid, args, *rest, **kwargs): + def search(self, cr, uid, args, *rest, **kwargs): ''' Overwrite search, as both acc_number and iban now can be filled, so the original base_iban 'search and search again fuzzy' tactic now can @@ -698,7 +698,7 @@ class res_partner_bank(orm.Model): # Original search results = super(res_partner_bank, self).search( - cursor, uid, newargs, *rest, **kwargs) + cr, uid, newargs, *rest, **kwargs) return results def read( @@ -721,23 +721,23 @@ class res_partner_bank(orm.Model): return records return records[0] - def check_iban(self, cursor, uid, ids): + def check_iban(self, cr, uid, ids, context=None): ''' Check IBAN number ''' - for bank_acc in self.browse(cursor, uid, ids): + for bank_acc in self.browse(cr, uid, ids, context=context): if bank_acc.state == 'iban' and bank_acc.acc_number: iban = sepa.IBAN(bank_acc.acc_number) if not iban.valid: return False return True - def get_bban_from_iban(self, cursor, uid, ids, context=None): + def get_bban_from_iban(self, cr, uid, ids, context=None): ''' Return the local bank account number aka BBAN from the IBAN. ''' res = {} - for record in self.browse(cursor, uid, ids, context): + for record in self.browse(cr, uid, ids, context): if not record.state == 'iban': res[record.id] = False else: @@ -763,7 +763,7 @@ class res_partner_bank(orm.Model): ) def onchange_domestic( - self, cursor, uid, ids, acc_number, + self, cr, uid, ids, acc_number, partner_id, country_id, context=None): ''' Trigger to find IBAN. When found: @@ -785,7 +785,7 @@ class res_partner_bank(orm.Model): # which can be overridden by the user. # 1. Use provided country_id (manually filled) if country_id: - country = country_obj.browse(cursor, uid, country_id) + country = country_obj.browse(cr, uid, country_id, context=context) country_ids = [country_id] # 2. Use country_id of found bank accounts # This can be usefull when there is no country set in the partners @@ -793,7 +793,7 @@ class res_partner_bank(orm.Model): # account itself before this method was triggered. elif ids and len(ids) == 1: partner_bank_obj = self.pool.get('res.partner.bank') - partner_bank_id = partner_bank_obj.browse(cursor, uid, ids[0]) + partner_bank_id = partner_bank_obj.browse(cr, uid, ids[0], context=context) if partner_bank_id.country_id: country = partner_bank_id.country_id country_ids = [country.id] @@ -804,12 +804,12 @@ class res_partner_bank(orm.Model): # bank account, hence the additional check. elif partner_id: partner_obj = self.pool.get('res.partner') - country = partner_obj.browse(cursor, uid, partner_id).country + country = partner_obj.browse(cr, uid, partner_id, context=context).country country_ids = country and [country.id] or [] # 4. Without any of the above, take the country from the company of # the handling user if not country_ids: - user = self.pool.get('res.users').browse(cursor, uid, uid) + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) # Try user companies partner (user no longer has address in 6.1) if (user.company_id and user.company_id.partner_id and @@ -830,7 +830,7 @@ class res_partner_bank(orm.Model): # Complete data with online database when available if country_ids: country = country_obj.browse( - cursor, uid, country_ids[0], context=context) + cr, uid, country_ids[0], context=context) values['country_id'] = country_ids[0] if country and country.code in sepa.IBAN.countries: try: @@ -842,7 +842,7 @@ class res_partner_bank(orm.Model): values['acc_number'] = unicode(iban_acc) values['state'] = 'iban' bank_id, country_id = get_or_create_bank( - self.pool, cursor, uid, + self.pool, cr, uid, info.bic or iban_acc.BIC_searchkey, name = info.bank ) @@ -911,7 +911,7 @@ class res_bank(orm.Model): ''' _inherit = 'res.bank' - def onchange_bic(self, cursor, uid, ids, bic, name, context=None): + def onchange_bic(self, cr, uid, ids, bic, name, context=None): ''' Trigger to auto complete other fields. ''' @@ -924,7 +924,7 @@ class res_bank(orm.Model): if address and address.country_id: country_id = self.pool.get('res.country').search( - cursor, uid, [('code','=',address.country_id)] + cr, uid, [('code','=',address.country_id)] ) country_id = country_id and country_id[0] or False else: diff --git a/account_banking/account_banking_view.xml b/account_banking/account_banking_view.xml index 8ba4a9bfc..6200cdfee 100644 --- a/account_banking/account_banking_view.xml +++ b/account_banking/account_banking_view.xml @@ -36,7 +36,6 @@ account.banking.account.settings.form account.banking.account.settings - form
account.banking.account.settings.tree account.banking.account.settings - tree @@ -99,7 +97,6 @@ account.banking.imported.file.form account.banking.imported.file - form @@ -124,7 +121,6 @@ account.banking.imported.file.tree account.banking.imported.file - tree @@ -182,7 +178,6 @@ account.bank.statement.tree.banking account.bank.statement - tree @@ -197,7 +192,6 @@ account.bank.statement - form @@ -292,7 +286,6 @@ res.partner.bank.form.banking-2 res.partner.bank - form @@ -311,7 +304,6 @@ res.bank.form.banking-1 res.bank - form @@ -322,7 +314,6 @@ Bank statement line tree view account.bank.statement.line - tree diff --git a/account_banking/banking_import_transaction.py b/account_banking/banking_import_transaction.py index cdbd34704..79801c84e 100644 --- a/account_banking/banking_import_transaction.py +++ b/account_banking/banking_import_transaction.py @@ -62,7 +62,7 @@ class banking_import_transaction(orm.Model): return [] digits = dp.get_precision('Account')(cr)[1] - amount = round(abs(trans.transferred_amount), digits) + 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) @@ -233,10 +233,6 @@ class banking_import_transaction(orm.Model): digits = dp.get_precision('Account')(cr)[1] partial = False - # Disabled splitting transactions for now - # TODO allow splitting in the interactive wizard - allow_splitting = False - # Search invoice on partner if partner_ids: candidates = [ @@ -276,7 +272,7 @@ class banking_import_transaction(orm.Model): candidates = [ x for x in move_lines if (is_zero(x.move_id, ((x.debit or 0.0) - (x.credit or 0.0)) - - trans.transferred_amount) + trans.statement_line_id.amount) and convert.str2date(x.date, '%Y-%m-%d') <= (convert.str2date(trans.execution_date, '%Y-%m-%d') + self.payment_window) @@ -292,7 +288,7 @@ class banking_import_transaction(orm.Model): # 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.transferred_amount) + trans.statement_line_id.amount) and convert.str2date(x.date, '%Y-%m-%d') <= (convert.str2date(trans.execution_date, '%Y-%m-%d') + self.payment_window)) @@ -343,7 +339,7 @@ class banking_import_transaction(orm.Model): trans2 = None if move_line and partial: - found = round(trans.transferred_amount, digits) + found = round(trans.statement_line_id.amount, digits) if abs(expected) == abs(found): partial = False # Last partial payment will not flag invoice paid without @@ -358,24 +354,6 @@ class banking_import_transaction(orm.Model): elif abs(expected) > abs(found): # Partial payment, reuse invoice _cache(move_line, expected - found) - elif abs(expected) < abs(found) and allow_splitting: - # Possible combined payments, need to split transaction to - # verify - _cache(move_line) - trans2 = self.copy( - cr, uid, trans.id, - dict( - transferred_amount = trans.transferred_amount - expected, - transaction = trans.transaction + 'b', - parent_id = trans.id, - ), context=context) - # update the current record - self.write(cr, uid, trans.id, dict( - transferred_amount = expected, - transaction = trans.transaction + 'a', - ), context) - # rebrowse the current record after writing - trans = self.browse(cr, uid, trans.id, context=context) if move_line: account_ids = [ x.id for x in bank_account_ids @@ -712,7 +690,7 @@ 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, + cr, uid, trans.statement_id.currency, me['transferred_amount'] - trans.transferred_amount): dupes.append(trans.id) @@ -810,6 +788,12 @@ class banking_import_transaction(orm.Model): move_info['invoice_ids'][0] ) return vals + + def hook_match_payment(self, cr, uid, transaction, log, context=None): + """ + To override in module 'account_banking_payment' + """ + return False def match(self, cr, uid, ids, results=None, context=None): if not ids: @@ -872,12 +856,6 @@ class banking_import_transaction(orm.Model): else: transaction = transactions[i] - if (transaction.statement_line_id and - transaction.statement_line_id.state == 'confirmed'): - raise orm.except_orm( - _("Cannot perform match"), - _("Cannot perform match on a confirmed transction")) - if transaction.local_account in error_accounts: results['trans_skipped_cnt'] += 1 if not injected: @@ -962,6 +940,44 @@ class banking_import_transaction(orm.Model): else: info[transaction.local_account][currency_code] = account_info + # Link accounting period + period_id = banktools.get_period( + self.pool, cr, uid, transaction.effective_date, + company, results['log']) + if not period_id: + results['trans_skipped_cnt'] += 1 + if not injected: + i += 1 + continue + + if transaction.statement_line_id: + if transaction.statement_line_id.state == 'confirmed': + raise orm.except_orm( + _("Cannot perform match"), + _("Cannot perform match on a confirmed transction")) + else: + values = { + 'name': '%s.%s' % (transaction.statement, transaction.transaction), + 'date': transaction.effective_date, + 'amount': transaction.transferred_amount, + 'statement_id': transaction.statement_id.id, + 'note': transaction.message, + 'ref': transaction.reference, + 'period_id': period_id, + 'currency': account_info.currency_id.id, + 'import_transaction_id': transaction.id, + 'account_id': ( + transaction.transferred_amount < 0 and + 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) + results['trans_loaded_cnt'] += 1 + transaction.write({'statement_line_id': statement_line_id}) + transaction.refresh() + if transaction.statement_id.id not in imported_statement_ids: + imported_statement_ids.append(transaction.statement_id.id) + # Final check: no coercion of currencies! if transaction.local_currency \ and account_info.currency_id.name != transaction.local_currency: @@ -971,8 +987,8 @@ class banking_import_transaction(orm.Model): ' uses different currency than the defined bank journal.' ) % { 'bank_account': transactions.local_account, - 'transaction_id': transaction.statement, - 'statement_id': transaction.transaction, + 'statement_id': transaction.statement, + 'transaction_id': transaction.transaction, } ) error_accounts[transaction.local_account] = True @@ -981,16 +997,6 @@ class banking_import_transaction(orm.Model): i += 1 continue - # Link accounting period - period_id = banktools.get_period( - self.pool, cr, uid, transaction.effective_date, - company, results['log']) - if not period_id: - results['trans_skipped_cnt'] += 1 - if not injected: - i += 1 - continue - # When bank costs are part of transaction itself, split it. if transaction.type != bt.BANK_COSTS and transaction.provision_costs: # Create new transaction for bank costs @@ -1022,13 +1028,12 @@ class banking_import_transaction(orm.Model): ), context=context) # rebrowse the current record after writing transaction = self.browse(cr, uid, transaction.id, context=context) - # Match full direct debit orders - if transaction.type == bt.DIRECT_DEBIT and has_payment: - move_info = self._match_debit_order( - cr, uid, transaction, results['log'], context) - if transaction.type == bt.STORNO and has_payment: - move_info = self._match_storno( - cr, uid, transaction, results['log'], context) + + # Match payment and direct debit orders + move_info_payment = self.hook_match_payment( + cr, uid, transaction, results['log'], context=context) + if move_info_payment: + move_info = move_info_payment # Allow inclusion of generated bank invoices if transaction.type == bt.BANK_COSTS: @@ -1075,7 +1080,7 @@ class banking_import_transaction(orm.Model): # Credit means payment... isn't it? if (not move_info - and transaction.transferred_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 @@ -1106,7 +1111,7 @@ class banking_import_transaction(orm.Model): # settings to overrule this. Note that you need to change # the internal type of these accounts to either 'payable' # or 'receivable' to enable usage like this. - if transaction.transferred_amount < 0: + if transaction.statement_line_id.amount < 0: if len(partner_banks) == 1: account_id = ( partner_banks[0].partner_id.property_account_payable and @@ -1122,7 +1127,7 @@ class banking_import_transaction(orm.Model): if len(partner_banks) != 1 or not account_id or account_id == def_rec_account_id: account_id = (account_info.default_debit_account_id and account_info.default_debit_account_id.id) - values = {} + values = {'account_id': account_id} self_values = {} if move_info: results['trans_matched_cnt'] += 1 @@ -1140,28 +1145,8 @@ class banking_import_transaction(orm.Model): len(partner_banks) == 1): values['partner_bank_id'] = partner_banks[0].id - if not transaction.statement_line_id: - values.update(dict( - name = '%s.%s' % (transaction.statement, transaction.transaction), - date = transaction.effective_date, - amount = transaction.transferred_amount, - statement_id = transaction.statement_id.id, - note = transaction.message, - ref = transaction.reference, - period_id = period_id, - currency = account_info.currency_id.id, - account_id = account_id, - import_transaction_id = transaction.id, - )) - - statement_line_id = statement_line_obj.create(cr, uid, values, context) - results['trans_loaded_cnt'] += 1 - self_values['statement_line_id'] = statement_line_id - if transaction.statement_id.id not in imported_statement_ids: - imported_statement_ids.append(transaction.statement_id.id) - else: - statement_line_obj.write( - cr, uid, transaction.statement_line_id.id, values, context) + statement_line_obj.write( + cr, uid, transaction.statement_line_id.id, values, context) self.write(cr, uid, transaction.id, self_values, context) if not injected: i += 1 @@ -1350,9 +1335,16 @@ class banking_import_transaction(orm.Model): 'parent_id': fields.many2one( 'banking.import.transaction', 'Split off from this transaction'), # match fields - 'match_type': fields.selection( - [('manual', 'Manual'), ('move','Move'), ('invoice', 'Invoice'), - ], 'Match type'), + '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'), 'match_multi': fields.function( _get_match_multi, method=True, string='Multi match', type='boolean'), @@ -1439,9 +1431,16 @@ class account_bank_statement_line(orm.Model): string='Possible duplicate import', readonly=True), 'match_type': fields.related( 'import_transaction_id', 'match_type', type='selection', - selection=[('manual', 'Manual'), ('move','Move'), - ('invoice', 'Invoice'), - ], + 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)'), + ], string='Match type', readonly=True,), 'state': fields.selection( [('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State', @@ -1755,14 +1754,14 @@ class account_bank_statement_line(orm.Model): class account_bank_statement(orm.Model): _inherit = 'account.bank.statement' - def _end_balance(self, cursor, user, ids, name, attr, context=None): + def _end_balance(self, cr, uid, ids, name, attr, context=None): """ This method taken from account/account_bank_statement.py and altered to take the statement line subflow into account """ res = {} - statements = self.browse(cursor, user, ids, context=context) + statements = self.browse(cr, uid, ids, context=context) for statement in statements: res[statement.id] = statement.balance_start diff --git a/account_banking/wizard/bank_import.py b/account_banking/wizard/bank_import.py index 9687b7dfd..873b439b7 100644 --- a/account_banking/wizard/bank_import.py +++ b/account_banking/wizard/bank_import.py @@ -28,9 +28,9 @@ 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. ''' -from osv import orm, fields import base64 import datetime +from openerp.osv import orm, fields from openerp.tools.translate import _ from openerp.addons.account_banking.parsers import models from openerp.addons.account_banking.parsers import convert @@ -97,13 +97,13 @@ class banking_import_line(orm.TransientModel): class banking_import(orm.TransientModel): _name = 'account.banking.bank.import' - def import_statements_file(self, cursor, uid, ids, context): + def import_statements_file(self, cr, uid, ids, context): ''' Import bank statements / bank transactions file. This method is a wrapper for the business logic on the transaction. The parser modules represent the decoding logic. ''' - banking_import = self.browse(cursor, uid, ids, context)[0] + banking_import = self.browse(cr, uid, ids, context)[0] statements_file = banking_import.file data = base64.decodestring(statements_file) @@ -125,10 +125,10 @@ class banking_import(orm.TransientModel): # Get the company company = (banking_import.company or - user_obj.browse(cursor, uid, uid, context).company_id) + user_obj.browse(cr, uid, uid, context).company_id) # Parse the file - statements = parser.parse(cursor, data) + statements = parser.parse(cr, data) if any([x for x in statements if not x.is_valid()]): raise orm.except_orm( @@ -137,7 +137,7 @@ class banking_import(orm.TransientModel): ) # Create the file now, as the statements need to be linked to it - import_id = statement_file_obj.create(cursor, uid, dict( + import_id = statement_file_obj.create(cr, uid, dict( company_id = company.id, file = statements_file, state = 'unfinished', @@ -184,7 +184,7 @@ class banking_import(orm.TransientModel): else: # Pull account info/currency account_info = banktools.get_company_bank_account( - self.pool, cursor, uid, statement.local_account, + self.pool, cr, uid, statement.local_account, statement.local_currency, company, results.log ) if not account_info: @@ -238,7 +238,7 @@ class banking_import(orm.TransientModel): # (e.g. a datetime string of the moment of import) # and have potential duplicates flagged by the # matching procedure - statement_ids = statement_obj.search(cursor, uid, [ + statement_ids = statement_obj.search(cr, uid, [ ('name', '=', statement.id), ('date', '=', convert.date2str(statement.date)), ]) @@ -252,7 +252,7 @@ class banking_import(orm.TransientModel): # Get the period for the statement (as bank statement object checks this) period_ids = period_obj.search( - cursor, uid, [ + cr, uid, [ ('company_id', '=', company.id), ('date_start', '<=', statement.date), ('date_stop', '>=', statement.date), @@ -270,7 +270,7 @@ class banking_import(orm.TransientModel): continue # Create the bank statement record - statement_id = statement_obj.create(cursor, uid, dict( + statement_id = statement_obj.create(cr, uid, dict( name = statement.id, journal_id = account_info.journal_id.id, date = convert.date2str(statement.date), @@ -302,21 +302,21 @@ class banking_import(orm.TransientModel): values['local_currency'] = statement.local_currency transaction_id = import_transaction_obj.create( - cursor, uid, values, context=context) + cr, uid, values, context=context) transaction_ids.append(transaction_id) results.stat_loaded_cnt += 1 - import_transaction_obj.match(cursor, uid, transaction_ids, results=results, context=context) + import_transaction_obj.match(cr, uid, transaction_ids, results=results, context=context) #recompute statement end_balance for validation statement_obj.button_dummy( - cursor, uid, imported_statement_ids, context=context) + cr, uid, imported_statement_ids, context=context) # Original code. Didn't take workflow logistics into account... # - #cursor.execute( + #cr.execute( # "UPDATE payment_order o " # "SET state = 'done', " # "date_done = '%s' " @@ -358,13 +358,13 @@ class banking_import(orm.TransientModel): ] text_log = '\n'.join(report + results.log) state = results.error_cnt and 'error' or 'ready' - statement_file_obj.write(cursor, uid, import_id, dict( + statement_file_obj.write(cr, uid, import_id, dict( 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(cursor, uid, [ids[0]], dict( + self.write(cr, uid, [ids[0]], dict( import_id = import_id, log = text_log, state = state, statement_ids = [(6, 0, imported_statement_ids)], ), context) diff --git a/account_banking/wizard/bank_import_view.xml b/account_banking/wizard/bank_import_view.xml index 2e04e29c6..0aca85c30 100644 --- a/account_banking/wizard/bank_import_view.xml +++ b/account_banking/wizard/bank_import_view.xml @@ -4,7 +4,6 @@ account.banking.bank.import account.banking.bank.import - form diff --git a/account_banking/wizard/banking_transaction_wizard.py b/account_banking/wizard/banking_transaction_wizard.py index dc6077469..0fc71677c 100644 --- a/account_banking/wizard/banking_transaction_wizard.py +++ b/account_banking/wizard/banking_transaction_wizard.py @@ -90,56 +90,33 @@ class banking_transaction_wizard(orm.TransientModel): statement_line_obj = self.pool.get('account.bank.statement.line') transaction_obj = self.pool.get('banking.import.transaction') - if not vals: + if not vals or not ids: return True + wiz = self.browse(cr, uid, ids[0], context=context) + # The following fields get never written # they are just triggers for manual matching # which populates regular fields on the transaction manual_invoice_ids = vals.pop('manual_invoice_ids', []) manual_move_line_ids = vals.pop('manual_move_line_ids', []) - # Support for writing fields.related is still flakey: - # https://bugs.launchpad.net/openobject-server/+bug/915975 - # Will do so myself. - - # Separate the related fields - transaction_vals = {} - wizard_vals = vals.copy() - for key in vals.keys(): - field = self._columns[key] - if (isinstance(field, fields.related) and - field._arg[0] == 'import_transaction_id'): - transaction_vals[field._arg[1]] = vals[key] - del wizard_vals[key] - - # write the related fields on the transaction model - if isinstance(ids, int): - ids = [ids] - for wizard in self.browse(cr, uid, ids, context=context): - if wizard.import_transaction_id: - transaction_obj.write( - cr, uid, wizard.import_transaction_id.id, - transaction_vals, context=context) - - # write other fields to the wizard model res = super(banking_transaction_wizard, self).write( - cr, uid, ids, wizard_vals, context=context) + cr, uid, ids, vals, context=context) + wiz.refresh() - # End of workaround for lp:915975 - - """ Process the logic of the written values """ + # Process the logic of the written values # An invoice is selected from multiple candidates if vals and 'invoice_id' in vals: - for wiz in self.browse(cr, uid, ids, context=context): - if (wiz.import_transaction_id.match_type == 'invoice' and + if (wiz.import_transaction_id.match_type == 'invoice' and wiz.import_transaction_id.invoice_id): - # the current value might apply - if (wiz.move_line_id and wiz.move_line_id.invoice and - wiz.move_line_id.invoice.id == wiz.invoice_id.id): - found = True - continue + found = False + # the current value might apply + if (wiz.move_line_id and wiz.move_line_id.invoice and + wiz.move_line_id.invoice == wiz.invoice_id): + found = True + else: # Otherwise, retrieve the move line for this invoice # Given the arity of the relation, there is are always # multiple possibilities but the move lines here are @@ -147,8 +124,8 @@ class banking_transaction_wizard(orm.TransientModel): # 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.id == - wiz.import_transaction_id.invoice_id.id): + 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) @@ -159,15 +136,12 @@ class banking_transaction_wizard(orm.TransientModel): }, context=context) found = True break - # Cannot match the invoice - if not found: - # transaction_obj.write( - # cr, uid, wiz.import_transaction_id.id, - # { 'invoice_id': False, }, context=context) - orm.except_orm( - _("No entry found for the selected invoice"), - _("No entry found for the selected invoice. " + - "Try manual reconciliation.")) + # Cannot match the invoice + if not found: + orm.except_orm( + _("No entry found for the selected invoice"), + _("No entry found for the selected invoice. " + + "Try manual reconciliation.")) if manual_move_line_ids or manual_invoice_ids: move_line_obj = self.pool.get('account.move.line') @@ -323,24 +297,9 @@ class banking_transaction_wizard(orm.TransientModel): {'duplicate': not wiz['duplicate']}, context=context) return self.create_act_window(cr, uid, ids, context=None) - def _get_default_match_type(self, cr, uid, context=None): - """ - Take initial value for the match type from the statement line - """ - res = False - if context and 'statement_line_id' in context: - res = self.pool.get('account.bank.statement.line').read( - cr, uid, context['statement_line_id'], - ['match_type'], context=context)['match_type'] - return res - def button_done(self, cr, uid, ids, context=None): return {'type': 'ir.actions.act_window_close'} - _defaults = { -# 'match_type': _get_default_match_type, - } - _columns = { 'name': fields.char('Name', size=64), 'statement_line_id': fields.many2one( @@ -392,8 +351,25 @@ class banking_transaction_wizard(orm.TransientModel): 'import_transaction_id', 'match_multi', type="boolean", string='Multiple matches'), 'match_type': fields.related( - 'import_transaction_id', 'match_type', - type="char", size=16, string='Match type', readonly=True), + 'import_transaction_id', 'match_type', type='selection', + 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)'), + ], + string='Match type', readonly=True), + 'manual_invoice_id': fields.many2one( + 'account.invoice', 'Match this invoice', + domain=[('reconciled', '=', False)]), + 'manual_move_line_id': fields.many2one( + 'account.move.line', 'Or match this entry', + domain=[('account_id.reconcile', '=', True), + ('reconcile_id', '=', False)]), 'manual_invoice_ids': fields.many2many( 'account.invoice', 'banking_transaction_wizard_account_invoice_rel', @@ -417,8 +393,6 @@ class banking_transaction_wizard(orm.TransientModel): string="Analytic Account"), 'move_currency_amount': fields.related('import_transaction_id','move_currency_amount', type='float', string='Match Currency Amount', readonly=True), - #'manual_payment_order_id': fields.many2one( - # 'payment.order', "Payment order to reconcile"), } banking_transaction_wizard() diff --git a/account_banking/wizard/banking_transaction_wizard.xml b/account_banking/wizard/banking_transaction_wizard.xml index cbcc3cf18..1e970eb7e 100644 --- a/account_banking/wizard/banking_transaction_wizard.xml +++ b/account_banking/wizard/banking_transaction_wizard.xml @@ -3,7 +3,6 @@ transaction.wizard.first - form banking.transaction.wizard diff --git a/account_banking/wizard/banktools.py b/account_banking/wizard/banktools.py index 2b8ad7f1d..94f0cd70c 100644 --- a/account_banking/wizard/banktools.py +++ b/account_banking/wizard/banktools.py @@ -51,7 +51,7 @@ def get_period(pool, cr, uid, date, company, log=None): return False return period_ids[0] -def get_bank_accounts(pool, cursor, uid, account_number, log, fail=False): +def get_bank_accounts(pool, cr, uid, account_number, log, fail=False): ''' Get the bank account with account number account_number ''' @@ -60,13 +60,13 @@ def get_bank_accounts(pool, cursor, uid, account_number, log, fail=False): return [] partner_bank_obj = pool.get('res.partner.bank') - bank_account_ids = partner_bank_obj.search(cursor, uid, [ + bank_account_ids = partner_bank_obj.search(cr, uid, [ ('acc_number', '=', account_number) ]) if not bank_account_ids: # SR 2012-02-19 does the search() override in res_partner_bank # provides this result on the previous query? - bank_account_ids = partner_bank_obj.search(cursor, uid, [ + bank_account_ids = partner_bank_obj.search(cr, uid, [ ('acc_number_domestic', '=', account_number) ]) if not bank_account_ids: @@ -76,7 +76,7 @@ def get_bank_accounts(pool, cursor, uid, account_number, log, fail=False): % dict(account_no=account_number) ) return [] - return partner_bank_obj.browse(cursor, uid, bank_account_ids) + 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 @@ -129,14 +129,14 @@ 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, cursor, uid, account_number, currency, +def get_company_bank_account(pool, cr, uid, account_number, currency, company, log): ''' Get the matching bank account for this company. Currency is the ISO code for the requested currency. ''' results = struct() - bank_accounts = get_bank_accounts(pool, cursor, uid, account_number, log, + bank_accounts = get_bank_accounts(pool, cr, uid, account_number, log, fail=True) if not bank_accounts: return False @@ -159,12 +159,12 @@ def get_company_bank_account(pool, cursor, uid, account_number, currency, # Find matching journal for currency journal_obj = pool.get('account.journal') - journal_ids = journal_obj.search(cursor, uid, [ + journal_ids = journal_obj.search(cr, uid, [ ('type', '=', 'bank'), ('currency.name', '=', currency or company.currency_id.name) ]) if currency == company.currency_id.name: - journal_ids_no_curr = journal_obj.search(cursor, uid, [ + journal_ids_no_curr = journal_obj.search(cr, uid, [ ('type', '=', 'bank'), ('currency', '=', False) ]) journal_ids.extend(journal_ids_no_curr) @@ -172,9 +172,9 @@ def get_company_bank_account(pool, cursor, uid, account_number, currency, criteria.append(('journal_id', 'in', journal_ids)) # Find bank account settings - bank_settings_ids = bank_settings_obj.search(cursor, uid, criteria) + bank_settings_ids = bank_settings_obj.search(cr, uid, criteria) if bank_settings_ids: - settings = bank_settings_obj.browse(cursor, uid, bank_settings_ids)[0] + settings = bank_settings_obj.browse(cr, uid, bank_settings_ids)[0] results.company_id = company results.journal_id = settings.journal_id @@ -192,7 +192,7 @@ def get_company_bank_account(pool, cursor, uid, account_number, currency, return results -def get_or_create_bank(pool, cursor, uid, bic, online=False, code=None, +def get_or_create_bank(pool, cr, uid, bic, online=False, code=None, name=None): ''' Find or create the bank with the provided BIC code. @@ -208,27 +208,27 @@ def get_or_create_bank(pool, cursor, uid, bic, online=False, code=None, if len(bic) < 8: # search key bank_ids = bank_obj.search( - cursor, uid, [ + cr, uid, [ ('bic', '=', bic[:6]) ]) if not bank_ids: bank_ids = bank_obj.search( - cursor, uid, [ + cr, uid, [ ('bic', 'ilike', bic + '%') ]) else: bank_ids = bank_obj.search( - cursor, uid, [ + cr, uid, [ ('bic', '=', bic) ]) if bank_ids and len(bank_ids) == 1: - banks = bank_obj.browse(cursor, uid, bank_ids) + banks = bank_obj.browse(cr, uid, bank_ids) return banks[0].id, banks[0].country.id country_obj = pool.get('res.country') country_ids = country_obj.search( - cursor, uid, [('code', '=', bic[4:6])] + cr, uid, [('code', '=', bic[4:6])] ) country_id = country_ids and country_ids[0] or False bank_id = False @@ -236,7 +236,7 @@ def get_or_create_bank(pool, cursor, uid, bic, online=False, code=None, if online: info, address = sepa.online.bank_info(bic) if info: - bank_id = bank_obj.create(cursor, uid, dict( + bank_id = bank_obj.create(cr, uid, dict( code = info.code, name = info.name, street = address.street, @@ -250,7 +250,7 @@ def get_or_create_bank(pool, cursor, uid, bic, online=False, code=None, info = struct(name=name, code=code) if not online or not bank_id: - bank_id = bank_obj.create(cursor, uid, dict( + bank_id = bank_obj.create(cr, uid, dict( code = info.code or 'UNKNOW', name = info.name or _('Unknown Bank'), country = country_id, diff --git a/account_banking_nl_clieop/__openerp__.py b/account_banking_nl_clieop/__openerp__.py index 6401de474..9e7aac5b9 100644 --- a/account_banking_nl_clieop/__openerp__.py +++ b/account_banking_nl_clieop/__openerp__.py @@ -37,5 +37,5 @@ ClieOp format is used by Dutch banks to batch national bank transfers. This module uses the account_banking logic. ''', - 'installable': False, + 'installable': True, } diff --git a/account_banking_nl_clieop/account_banking_nl_clieop.py b/account_banking_nl_clieop/account_banking_nl_clieop.py index b0da0bb74..97925c8f4 100644 --- a/account_banking_nl_clieop/account_banking_nl_clieop.py +++ b/account_banking_nl_clieop/account_banking_nl_clieop.py @@ -1,6 +1,7 @@ ############################################################################## # # Copyright (C) 2009 EduSense BV (). +# (C) 2011 - 2013 Therp BV (). # All Rights Reserved # # This program is free software: you can redistribute it and/or modify @@ -18,11 +19,12 @@ # ############################################################################## -from osv import osv, fields from datetime import date -from tools.translate import _ +from openerp.osv import orm, fields +from openerp.tools.translate import _ -class clieop_export(osv.osv): + +class clieop_export(orm.Model): '''ClieOp3 Export''' _name = 'banking.export.clieop' _description = __doc__ @@ -80,7 +82,7 @@ class clieop_export(osv.osv): last = 1 last_ids = self.search(cr, uid, [ ('date_generated', '=', - fields.date.context_today(cr,uid,context)) + fields.date.context_today(self, cr,uid,context)) ], context=context) if last_ids: last = 1 + max([x['daynumber'] for x in self.read( @@ -94,6 +96,3 @@ class clieop_export(osv.osv): 'state': 'draft', 'daynumber': get_daynr, } -clieop_export() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking_nl_clieop/account_banking_nl_clieop.xml b/account_banking_nl_clieop/account_banking_nl_clieop.xml index f40861f8a..8b4658b7f 100644 --- a/account_banking_nl_clieop/account_banking_nl_clieop.xml +++ b/account_banking_nl_clieop/account_banking_nl_clieop.xml @@ -11,7 +11,6 @@ account.banking.export.clieop.form banking.export.clieop - form @@ -48,7 +47,6 @@ account.banking.export.clieop.tree banking.export.clieop - tree diff --git a/account_banking_nl_clieop/wizard/export_clieop.py b/account_banking_nl_clieop/wizard/export_clieop.py index ba25f246b..915c46bb6 100644 --- a/account_banking_nl_clieop/wizard/export_clieop.py +++ b/account_banking_nl_clieop/wizard/export_clieop.py @@ -2,6 +2,7 @@ ############################################################################## # # Copyright (C) 2009 EduSense BV (). +# 2011 - 2013 Therp BV (). # All Rights Reserved # # This program is free software: you can redistribute it and/or modify @@ -21,21 +22,22 @@ import base64 from datetime import datetime, date, timedelta -from osv import osv, fields -from tools.translate import _ -import netsvc -from account_banking import sepa -import clieop +from openerp.osv import orm, fields +from openerp.tools.translate import _ +from openerp import netsvc +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT +from openerp.addons.account_banking import sepa +from openerp.addons.account_banking_nl_clieop.wizard import clieop -def strpdate(arg, format='%Y-%m-%d'): +def strpdate(arg): '''shortcut''' - return datetime.strptime(arg, format).date() + return datetime.strptime(arg, DEFAULT_SERVER_DATE_FORMAT).date() -def strfdate(arg, format='%Y-%m-%d'): +def strfdate(arg): '''shortcut''' - return arg.strftime(format) + return arg.strftime(DEFAULT_SERVER_DATE_FORMAT) -class banking_export_clieop_wizard(osv.osv_memory): +class banking_export_clieop_wizard(orm.TransientModel): _name = 'banking.export.clieop.wizard' _description = 'Client Opdrachten Export' _columns = { @@ -151,21 +153,17 @@ class banking_export_clieop_wizard(osv.osv_memory): ), } - _defaults = { - 'test': True, - } - - def create(self, cursor, uid, vals, context=None): + def create(self, cr, uid, vals, context=None): ''' Retrieve a sane set of default values based on the payment orders from the context. ''' if 'batchtype' not in vals: - self.check_orders(cursor, uid, vals, context) + self.check_orders(cr, uid, vals, context) return super(banking_export_clieop_wizard, self).create( - cursor, uid, vals, context) + cr, uid, vals, context) - def check_orders(self, cursor, uid, vals, context): + def check_orders(self, cr, uid, vals, context): ''' Check payment type for all orders. @@ -177,14 +175,14 @@ class banking_export_clieop_wizard(osv.osv_memory): Also mind that rates for batches are way higher than those for transactions. It pays to limit the number of batches. ''' - today = date.today() + today = strpdate(fields.date.context_today(self, cr, uid, context=context)) payment_order_obj = self.pool.get('payment.order') # Payment order ids are provided in the context payment_order_ids = context.get('active_ids', []) runs = {} # Only orders of same type can be combined - payment_orders = payment_order_obj.browse(cursor, uid, payment_order_ids) + payment_orders = payment_order_obj.browse(cr, uid, payment_order_ids) for payment_order in payment_orders: payment_type = payment_order.mode.type.code @@ -194,8 +192,8 @@ class banking_export_clieop_wizard(osv.osv_memory): runs[payment_type] = [payment_order] if payment_order.date_prefered == 'fixed': - if payment_order.date_planned: - execution_date = strpdate(payment_order.date_planned) + if payment_order.date_scheduled: + execution_date = strpdate(payment_order.date_scheduled) else: execution_date = today elif payment_order.date_prefered == 'now': @@ -212,12 +210,12 @@ class banking_export_clieop_wizard(osv.osv_memory): else: execution_date = today if execution_date and execution_date >= max_date: - raise osv.except_osv( + raise orm.except_orm( _('Error'), _('You can\'t create ClieOp orders more than 30 days in advance.') ) if len(runs) != 1: - raise osv.except_osv( + raise orm.except_orm( _('Error'), _('You can only combine payment orders of the same type') ) @@ -231,12 +229,12 @@ class banking_export_clieop_wizard(osv.osv_memory): 'state': 'create', }) - def create_clieop(self, cursor, uid, ids, context): + def create_clieop(self, cr, uid, ids, context): ''' Wizard to actually create the ClieOp3 file ''' payment_order_obj = self.pool.get('payment.order') - clieop_export = self.browse(cursor, uid, ids, context)[0] + clieop_export = self.browse(cr, uid, ids, context)[0] clieopfile = None for payment_order in clieop_export.payment_order_ids: if not clieopfile: @@ -253,7 +251,7 @@ class banking_export_clieop_wizard(osv.osv_memory): else: our_account_nr = payment_order.mode.bank_id.acc_number if not our_account_nr: - raise osv.except_osv( + raise orm.except_orm( _('Error'), _('Your bank account has to have a valid account number') ) @@ -267,7 +265,7 @@ class banking_export_clieop_wizard(osv.osv_memory): accountno_sender = our_account_nr, seqno = self.pool.get( 'banking.export.clieop').get_daynr( - cursor, uid, context=context), + cr, uid, context=context), test = clieop_export['test'] ) @@ -291,7 +289,7 @@ class banking_export_clieop_wizard(osv.osv_memory): for line in payment_order.line_ids: # Check on missing partner of bank account (this can happen!) if not line.bank_id or not line.bank_id.partner_id: - raise osv.except_osv( + raise orm.except_orm( _('Error'), _('There is insufficient information.\r\n' 'Both destination address and account ' @@ -314,7 +312,7 @@ class banking_export_clieop_wizard(osv.osv_memory): # Is this an IBAN account? if iban.valid: if iban.countrycode != 'NL': - raise osv.except_osv( + raise orm.except_orm( _('Error'), _('You cannot send international bank transfers ' 'through ClieOp3!') @@ -331,7 +329,7 @@ class banking_export_clieop_wizard(osv.osv_memory): # Generate the specifics of this clieopfile order = clieopfile.order file_id = self.pool.get('banking.export.clieop').create( - cursor, uid, dict( + cr, uid, dict( filetype = order.name_transactioncode, identification = order.identification, prefered_date = strfdate(order.preferred_execution_date), @@ -346,7 +344,7 @@ class banking_export_clieop_wizard(osv.osv_memory): [6, 0, [x.id for x in clieop_export['payment_order_ids']]] ], ), context) - self.write(cursor, uid, [ids[0]], dict( + self.write(cr, uid, [ids[0]], dict( filetype = order.name_transactioncode, testcode = order.testcode, file_id = file_id, @@ -364,31 +362,27 @@ class banking_export_clieop_wizard(osv.osv_memory): 'res_id': ids[0] or False, } - def cancel_clieop(self, cursor, uid, ids, context): + def cancel_clieop(self, cr, uid, ids, context): ''' Cancel the ClieOp: just drop the file ''' - clieop_export = self.read(cursor, uid, ids, ['file_id'], context)[0] - self.pool.get('banking.export.clieop').unlink(cursor, uid, clieop_export['file_id'][0]) + clieop_export = self.read(cr, uid, ids, ['file_id'], context)[0] + self.pool.get('banking.export.clieop').unlink(cr, uid, clieop_export['file_id'][0]) return {'type': 'ir.actions.act_window_close'} - def save_clieop(self, cursor, uid, ids, context): + def save_clieop(self, cr, uid, ids, context): ''' Save the ClieOp: mark all payments in the file as 'sent', if not a test ''' clieop_export = self.browse( - cursor, uid, ids, context)[0] + cr, uid, ids, context)[0] if not clieop_export['test']: clieop_obj = self.pool.get('banking.export.clieop') payment_order_obj = self.pool.get('payment.order') clieop_file = clieop_obj.write( - cursor, uid, clieop_export['file_id'].id, {'state': 'sent'} + cr, uid, clieop_export['file_id'].id, {'state': 'sent'} ) wf_service = netsvc.LocalService('workflow') for order in clieop_export['payment_order_ids']: - wf_service.trg_validate(uid, 'payment.order', order.id, 'sent', cursor) + wf_service.trg_validate(uid, 'payment.order', order.id, 'sent', cr) return {'type': 'ir.actions.act_window_close'} - -banking_export_clieop_wizard() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking_nl_clieop/wizard/export_clieop_view.xml b/account_banking_nl_clieop/wizard/export_clieop_view.xml index 3c6754e51..34b3e4cfe 100644 --- a/account_banking_nl_clieop/wizard/export_clieop_view.xml +++ b/account_banking_nl_clieop/wizard/export_clieop_view.xml @@ -4,7 +4,6 @@ banking.export.clieop.wizard.view banking.export.clieop.wizard - form diff --git a/account_banking_nl_girotel/girotel.py b/account_banking_nl_girotel/girotel.py index 9b0e56087..792770f19 100644 --- a/account_banking_nl_girotel/girotel.py +++ b/account_banking_nl_girotel/girotel.py @@ -45,6 +45,7 @@ As a counter measure, all imported data is converted to SWIFT-format before usag from account_banking.parsers import models from account_banking.parsers.convert import str2date, to_swift from tools.translate import _ +import re import csv bt = models.mem_bank_transaction @@ -105,6 +106,13 @@ class transaction_message(object): self.date = str2date(self.date, '%Y%m%d') if self.direction == 'A': self.transferred_amount = -float(self.transferred_amount) + if (self.transfer_type == 'VZ' + and (not self.remote_account or self.remote_account == '0') + and (not self.message or re.match('^\s*$', self.message)) + and self.remote_owner.startswith('TOTAAL ')): + self.transfer_type = 'PB' + self.message = self.remote_owner + self.remove_owner = False else: self.transferred_amount = float(self.transferred_amount) self.local_account = self.local_account.zfill(10) @@ -140,7 +148,8 @@ class transaction(models.mem_bank_transaction): 'GT': bt.ORDER, 'IC': bt.DIRECT_DEBIT, 'OV': bt.ORDER, - 'VZ': bt.PAYMENT_BATCH, + 'VZ': bt.ORDER, + 'PB': bt.PAYMENT_BATCH, } def __init__(self, line, *args, **kwargs): @@ -171,11 +180,14 @@ class transaction(models.mem_bank_transaction): 4. Cash withdrawals from banks are too not seen as a transfer between two accounts - the cash exits the banking system. These withdrawals have their transfer_type set to 'GM'. + 5. Aggregated payment batches. These transactions have transfer type + 'VZ' natively but are changed to 'PB' while parsing. These transactions + have no remote account. ''' return bool(self.transferred_amount and self.execution_date and ( self.remote_account or self.transfer_type in [ - 'DV', 'BT', 'BA', 'GM', + 'DV', 'PB', 'BT', 'BA', 'GM', ])) def refold_message(self, message): diff --git a/account_banking_payment/__openerp__.py b/account_banking_payment/__openerp__.py index 98507a061..af1ecab2e 100644 --- a/account_banking_payment/__openerp__.py +++ b/account_banking_payment/__openerp__.py @@ -37,6 +37,7 @@ 'data': [ 'view/account_payment.xml', 'view/banking_transaction_wizard.xml', + 'view/payment_mode.xml', 'view/payment_mode_type.xml', 'view/bank_payment_manual.xml', 'data/payment_mode_type.xml', @@ -53,5 +54,5 @@ account_banking_nl_clieop ''', 'auto_install': True, - 'installable': False, + 'installable': True, } diff --git a/account_banking_payment/model/__init__.py b/account_banking_payment/model/__init__.py index 02640f10d..9908c8e01 100644 --- a/account_banking_payment/model/__init__.py +++ b/account_banking_payment/model/__init__.py @@ -4,7 +4,6 @@ import payment_mode import payment_mode_type import payment_order_create import banking_import_transaction -import account_bank_statement_line import banking_transaction_wizard import bank_payment_manual import banking_import_line diff --git a/account_banking_payment/model/account_payment.py b/account_banking_payment/model/account_payment.py index 1e05e9a77..30a283f33 100644 --- a/account_banking_payment/model/account_payment.py +++ b/account_banking_payment/model/account_payment.py @@ -73,6 +73,8 @@ class payment_order(orm.Model): 'line_ids': fields.one2many( 'payment.line', 'order_id', 'Payment lines', states={ + 'open': [('readonly', True)], + 'cancel': [('readonly', True)], 'sent': [('readonly', True)], 'rejected': [('readonly', True)], 'done': [('readonly', True)] @@ -172,32 +174,10 @@ class payment_order(orm.Model): ]) payment_line_obj.write(cr, uid, line_ids, kwargs) - def set_to_draft(self, cr, uid, ids, *args): - ''' - Set both self and payment lines to state 'draft'. - ''' - self._write_payment_lines(cr, uid, ids, export_state='draft') - return super(payment_order, self).set_to_draft( - cr, uid, ids, *args - ) - - def action_sent(self, cr, uid, ids, context=None): - ''' - Set both self and payment lines to state 'sent'. - ''' - self._write_payment_lines(cr, uid, ids, export_state='sent') - self.write(cr, uid, ids, { - 'state': 'sent', - 'date_sent': fields.date.context_today( - self, cr, uid, context=context), - }, context=context) - return True - def action_rejected(self, cr, uid, ids, *args): ''' Set both self and payment lines to state 'rejected'. ''' - self._write_payment_lines(cr, uid, ids, export_state='rejected') wf_service = netsvc.LocalService('workflow') for id in ids: wf_service.trg_validate(uid, 'payment.order', id, 'rejected', cr) @@ -209,32 +189,172 @@ class payment_order(orm.Model): ''' self._write_payment_lines( cr, uid, ids, - export_state='done', date_done=fields.date.context_today(self, cr, uid)) return super(payment_order, self).set_done( cr, uid, ids, *args ) - """ - Hooks for processing direct debit orders, such as implemented in - account_direct_debit module. - """ - def debit_reconcile_transfer( - self, cr, uid, payment_order_id, amount, currency, context=None): + def debit_reconcile_transfer(self, cr, uid, payment_order_id, + amount, currency, context=None): """ - Reconcile the payment order if the amount is correct. Return the - id of the reconciliation. + During import of bank statements, create the reconcile on the transfer + account containing all the open move lines on the transfer account. """ - raise orm.except_orm( - _("Cannot reconcile"), - _("Cannot reconcile debit order: "+ - "Not implemented.")) + move_line_obj = self.pool.get('account.move.line') + order = self.browse(cr, uid, payment_order_id, context) + line_ids = [] + reconcile_id = False + if not order.line_ids[0].transit_move_line_id: + wf_service = netsvc.LocalService('workflow') + wf_service.trg_validate( + uid, 'payment.order', payment_order_id, 'done', cr) + return False + for order_line in order.line_ids: + for line in order_line.transit_move_line_id.move_id.line_id: + if line.account_id.type == 'other' and not line.reconcile_id: + line_ids.append(line.id) + if self.pool.get('res.currency').is_zero( + cr, uid, currency, + move_line_obj.get_balance(cr, uid, line_ids) - amount): + reconcile_id = self.pool.get('account.move.reconcile').create( + cr, uid, + {'type': 'auto', 'line_id': [(6, 0, line_ids)]}, + context) + # set direct debit order to finished state + wf_service = netsvc.LocalService('workflow') + wf_service.trg_validate( + uid, 'payment.order', payment_order_id, 'done', cr) + return reconcile_id + + def debit_unreconcile_transfer(self, cr, uid, payment_order_id, reconcile_id, + amount, currency, context=None): + """ + Due to a cancelled bank statements import, unreconcile the move on + the transfer account. Delegate the conditions to the workflow. + Raise on failure for rollback. + + Workflow appears to return False even on success so we just check + the order's state that we know to be set to 'sent' in that case. + """ + self.pool.get('account.move.reconcile').unlink( + cr, uid, [reconcile_id], context=context) + netsvc.LocalService('workflow').trg_validate( + uid, 'payment.order', payment_order_id, 'undo_done', cr) + state = self.pool.get('payment.order').read( + cr, uid, payment_order_id, ['state'], context=context)['state'] + if state != 'sent': + raise orm.except_orm( + _("Cannot unreconcile"), + _("Cannot unreconcile payment order: "+ + "Workflow will not allow it.")) + return True + + def test_undo_done(self, cr, uid, ids, context=None): + """ + Called from the workflow. Used to unset done state on + payment orders that were reconciled with bank transfers + which are being cancelled. + + Test if the payment order has not been reconciled. Depends + on the restriction that transit move lines should use an + account of type 'other', and on the restriction of payment + and debit orders that they only take moves on accounts + payable/receivable. + """ + for order in self.browse(cr, uid, ids, context=context): + for order_line in order.line_ids: + if order_line.transit_move_line_id.move_id: + for line in order_line.transit_move_line_id.move_id.line_id: + if (line.account_id.type == 'other' and + line.reconcile_id): + return False + return True + + def action_sent(self, cr, uid, ids, context=None): + """ + Create the moves that pay off the move lines from + the debit order. This happens when the debit order file is + generated. + """ + account_move_obj = self.pool.get('account.move') + account_move_line_obj = self.pool.get('account.move.line') + payment_line_obj = self.pool.get('payment.line') + labels = { + 'payment': _('Payment order'), + 'debit': _('Direct debit order'), + } + for order in self.browse(cr, uid, ids, context=context): + for line in order.line_ids: + # basic checks + if not line.move_line_id: + raise orm.except_orm( + _('Error'), + _('No move line provided for line %s') % line.name) + if line.move_line_id.reconcile_id: + raise orm.except_orm( + _('Error'), + _('Move line %s has already been paid/reconciled') % + line.move_line_id.name + ) + + move_id = account_move_obj.create(cr, uid, { + 'journal_id': order.mode.transfer_journal_id.id, + 'name': '%s %s' % (labels[order.payment_order_type], + line.move_line_id.move_id.name), + 'reference': '%s%s' % (order.payment_order_type[:3].upper(), + line.move_line_id.move_id.name), + }, context=context) + + # TODO: take multicurrency into account + + # create the debit move line on the transfer account + vals = { + 'name': _('%s for %s') % ( + labels[order.payment_order_type], + line.move_line_id.invoice and + line.move_line_id.invoice.number or + line.move_line_id.name), + 'move_id': move_id, + 'partner_id': line.partner_id.id, + 'account_id': order.mode.transfer_account_id.id, + 'credit': (order.payment_order_type == 'payment' + and line.amount or 0.0), + 'debit': (order.payment_order_type == 'debit' + and line.amount or 0.0), + 'date': fields.date.context_today( + self, cr, uid, context=context), + } + transfer_move_line_id = account_move_line_obj.create( + cr, uid, vals, context=context) + + # create the debit move line on the receivable account + vals.update({ + 'account_id': line.move_line_id.account_id.id, + 'credit': (order.payment_order_type == 'debit' + and line.amount or 0.0), + 'debit': (order.payment_order_type == 'payment' + and line.amount or 0.0), + }) + reconcile_move_line_id = account_move_line_obj.create( + cr, uid, vals, context=context) + + # register the debit move line on the payment line + # and call reconciliation on it + payment_line_obj.write( + cr, uid, line.id, + {'transit_move_line_id': reconcile_move_line_id}, + context=context) + + payment_line_obj.debit_reconcile( + cr, uid, line.id, context=context) + account_move_obj.post(cr, uid, [move_id], context=context) + + # State field is written by act_sent_wait + self.write(cr, uid, ids, { + 'date_sent': fields.date.context_today( + self, cr, uid, context=context), + }, context=context) + + return True + - def debit_unreconcile_transfer( - self, cr, uid, payment_order_id, reconcile_id, amount, currency, - context=None): - """ Unreconcile the payment_order if at all possible """ - raise orm.except_orm( - _("Cannot unreconcile"), - _("Cannot unreconcile debit order: "+ - "Not implemented.")) diff --git a/account_banking_payment/model/banking_import_transaction.py b/account_banking_payment/model/banking_import_transaction.py index 7b295b203..59fd5b6c9 100644 --- a/account_banking_payment/model/banking_import_transaction.py +++ b/account_banking_payment/model/banking_import_transaction.py @@ -27,37 +27,44 @@ from openerp.osv import orm, fields from openerp import netsvc from openerp.tools.translate import _ from openerp.addons.decimal_precision import decimal_precision as dp +from openerp.addons.account_banking.parsers.models import mem_bank_transaction as bt class banking_import_transaction(orm.Model): _inherit = 'banking.import.transaction' - def _match_debit_order( - self, cr, uid, trans, log, context=None): + def _match_payment_order( + self, cr, uid, trans, log, order_type='payment', context=None): - def is_zero(total): + def equals_order_amount(payment_order, transferred_amount): + if (not hasattr(payment_order, 'payment_order_type') + or payment_order.payment_order_type == 'payment'): + sign = 1 + else: + sign = -1 + total = payment_order.total + sign * transferred_amount return self.pool.get('res.currency').is_zero( - cr, uid, trans.statement_id.currency, total) + cr, uid, trans.statement_line_id.statement_id.currency, total) payment_order_obj = self.pool.get('payment.order') order_ids = payment_order_obj.search( - cr, uid, [('payment_order_type', '=', 'debit'), + cr, uid, [('payment_order_type', '=', order_type), ('state', '=', 'sent'), ('date_sent', '<=', trans.execution_date), ], limit=0, context=context) orders = payment_order_obj.browse(cr, uid, order_ids, context) candidates = [x for x in orders if - is_zero(x.total - trans.transferred_amount) and - x.line_ids and x.line_ids[0].debit_move_line_id] + equals_order_amount(x, trans.statement_line_id.amount)] if len(candidates) > 0: # retrieve the common account_id, if any account_id = False - for line in candidates[0].line_ids[0].debit_move_line_id.move_id.line_id: - if line.account_id.type == 'other': - account_id = line.account_id.id - break + if (candidates[0].line_ids[0].transit_move_line_id): + for line in candidates[0].line_ids[0].transit_move_line_id.move_id.line_id: + if line.account_id.type == 'other': + account_id = line.account_id.id + break return dict( move_line_ids = False, match_type = 'payment_order', @@ -82,7 +89,7 @@ class banking_import_transaction(orm.Model): # stornos MUST have an exact match if len(line_ids) == 1: account_id = payment_line_obj.get_storno_account_id( - cr, uid, line_ids[0], trans.transferred_amount, + cr, uid, line_ids[0], trans.statement_line_id.amount, trans.statement_id.currency, context=None) if account_id: return dict( @@ -114,7 +121,7 @@ class banking_import_transaction(orm.Model): x for x in payment_lines if x.communication == trans.reference and round(x.amount, digits) == -round( - trans.transferred_amount, digits) + trans.statement_line_id.amount, digits) and trans.remote_account in (x.bank_id.acc_number, x.bank_id.acc_number_domestic) ] @@ -171,10 +178,6 @@ class banking_import_transaction(orm.Model): raise orm.except_orm( _("Cannot reconcile"), _("Cannot reconcile: no direct debit order")) - if transaction.payment_order_id.payment_order_type != 'debit': - raise orm.except_orm( - _("Cannot reconcile"), - _("Reconcile payment order not implemented")) reconcile_id = payment_order_obj.debit_reconcile_transfer( cr, uid, transaction.payment_order_id.id, @@ -195,7 +198,6 @@ class banking_import_transaction(orm.Model): payment_line_obj = self.pool.get('payment.line') payment_line_obj.write( cr, uid, transaction.payment_line_id.id, { - 'export_state': 'done', 'date_done': transaction.statement_line_id.date, } ) @@ -232,11 +234,12 @@ class banking_import_transaction(orm.Model): if not transaction.payment_order_id: raise orm.except_orm( _("Cannot unreconcile"), - _("Cannot unreconcile: no direct debit order")) - if transaction.payment_order_id.payment_order_type != 'debit': + _("Cannot unreconcile: no payment or direct debit order")) + if not transaction.statement_line_id.reconcile_id: raise orm.except_orm( _("Cannot unreconcile"), - _("Unreconcile payment order not implemented")) + _("Payment orders without transfer move lines cannot be " + "unreconciled this way")) return payment_order_obj.debit_unreconcile_transfer( cr, uid, transaction.payment_order_id.id, transaction.statement_line_id.reconcile_id.id, @@ -302,17 +305,6 @@ class banking_import_transaction(orm.Model): cr, uid, transaction.payment_line_id.id, context) _columns = { - 'match_type': fields.selection( - # Add payment and storno types - [ - ('manual', 'Manual'), - ('move','Move'), - ('invoice', 'Invoice'), - ('payment', 'Payment'), - ('payment_order', 'Payment order'), - ('storno', 'Storno'), - ], - 'Match type'), 'payment_order_ids': fields.many2many( 'payment.order', 'banking_transaction_payment_order_rel', 'order_id', 'transaction_id', 'Payment orders'), @@ -334,14 +326,14 @@ class banking_import_transaction(orm.Model): return res def clear_and_write(self, cr, uid, ids, vals=None, context=None): - super(banking_import_transaction, self).clear_and_write( + write_vals = { + 'payment_line_id': False, + 'payment_order_id': False, + 'payment_order_ids': [(6, 0, [])], + } + write_vals.update(vals or {}) + return super(banking_import_transaction, self).clear_and_write( cr, uid, ids, vals=vals, context=context) - return self.write( - cr, uid, ids, { - 'payment_line_id': False, - 'payment_order_ids': [(6, 0, [])], - }, - context=context) def move_info2values(self, move_info): vals = super(banking_import_transaction, self).move_info2values( @@ -356,11 +348,25 @@ class banking_import_transaction(orm.Model): ) return vals - def match(self, cr, uid, ids, results=None, context=None): - res = super(banking_import_transaction, self).match( - cr, uid, ids, results=results, context=context) - - return res + def hook_match_payment(self, cr, uid, transaction, log, context=None): + """ + Called from match() in the core module. + Match payment batches, direct debit orders and stornos + """ + move_info = False + if transaction.type == bt.PAYMENT_BATCH: + move_info = self._match_payment_order( + cr, uid, transaction, log, + order_type='payment', context=context) + elif transaction.type == bt.DIRECT_DEBIT: + move_info = self._match_payment_order( + cr, uid, transaction, log, + order_type='debit', context=context) + elif transaction.type == bt.STORNO: + move_info = self._match_storno( + cr, uid, transaction, log, + context=context) + return move_info def __init__(self, pool, cr): """ @@ -369,14 +375,18 @@ class banking_import_transaction(orm.Model): """ super(banking_import_transaction, self).__init__(pool, cr) - banking_import_transaction.confirm_map.update({ + self.confirm_map.update({ 'storno': banking_import_transaction._confirm_storno, 'payment_order': banking_import_transaction._confirm_payment_order, 'payment': banking_import_transaction._confirm_payment, + 'payment_order_manual': banking_import_transaction._confirm_payment_order, + 'payment_manual': banking_import_transaction._confirm_payment, }) - banking_import_transaction.cancel_map.update({ + self.cancel_map.update({ 'storno': banking_import_transaction._cancel_storno, 'payment_order': banking_import_transaction._cancel_payment_order, 'payment': banking_import_transaction._cancel_payment, + 'payment_order_manual': banking_import_transaction._cancel_payment_order, + 'payment_manual': banking_import_transaction._cancel_payment, }) diff --git a/account_banking_payment/model/banking_transaction_wizard.py b/account_banking_payment/model/banking_transaction_wizard.py index 470a81c81..a2f7a359b 100644 --- a/account_banking_payment/model/banking_transaction_wizard.py +++ b/account_banking_payment/model/banking_transaction_wizard.py @@ -24,10 +24,59 @@ ############################################################################## from openerp.osv import orm, fields +from openerp.tools.translate import _ class banking_transaction_wizard(orm.TransientModel): _inherit = 'banking.transaction.wizard' + + def write(self, cr, uid, ids, vals, context=None): + """ + Check for manual payment orders or lines + """ + if not vals or not ids: + return True + manual_payment_order_id = vals.pop('manual_payment_order_id', False) + manual_payment_line_id = vals.pop('manual_payment_line_id', False) + res = super(banking_transaction_wizard, self).write( + cr, uid, ids, vals, context=context) + if manual_payment_order_id or manual_payment_line_id: + transaction_id = self.browse( + cr, uid, ids[0], + context=context).import_transaction_id + write_vals = {} + if manual_payment_order_id: + payment_order = self.pool.get('payment.order').browse( + cr, uid, manual_payment_order_id, + context=context) + if payment_order.payment_order_type == 'payment': + sign = 1 + else: + sign = -1 + total = (payment_order.total + sign * + transaction_id.statement_line_id.amount) + if not self.pool.get('res.currency').is_zero( + cr, uid, transaction_id.statement_line_id.statement_id.currency, total): + raise orm.except_orm( + _('Error'), + _('When matching a payment order, the amounts have to ' + 'match exactly')) + + if payment_order.mode and payment_order.mode.transfer_account_id: + transaction_id.statement_line_id.write({ + 'account_id': payment_order.mode.transfer_account_id.id, + }) + write_vals.update( + {'payment_order_id': manual_payment_order_id, + 'match_type': 'payment_order_manual'}) + else: + write_vals.update( + {'payment_line_id': manual_payment_line_id, + 'match_type': 'payment_manual'}) + self.pool.get('banking.import.transaction').clear_and_write( + cr, uid, transaction_id.id, write_vals, context=context) + return res + _columns = { 'payment_line_id': fields.related( 'import_transaction_id', 'payment_line_id', @@ -42,4 +91,13 @@ class banking_transaction_wizard(orm.TransientModel): 'import_transaction_id', 'payment_order_id', string="Payment order to reconcile", type='many2one', relation='payment.order'), + 'manual_payment_order_id': fields.many2one( + 'payment.order', 'Match this payment order', + domain=[('state', '=', 'sent')]), + 'manual_payment_line_id': fields.many2one( + 'payment.line', 'Match this payment line', + domain=[ + ('order_id.state', '=', 'sent'), + ('date_done', '=', False), + ]), } diff --git a/account_banking_payment/model/payment_line.py b/account_banking_payment/model/payment_line.py index 30bef3564..3abae5ff0 100644 --- a/account_banking_payment/model/payment_line.py +++ b/account_banking_payment/model/payment_line.py @@ -24,11 +24,12 @@ ############################################################################## from openerp.osv import orm, fields - +from openerp import netsvc +from openerp.tools.translate import _ class payment_line(orm.Model): ''' - Add extra export_state and date_done fields; make destination bank account + Add some fields; make destination bank account mandatory, as it makes no sense to send payments into thin air. Edit: Payments can be by cash too, which is prohibited by mandatory bank accounts. @@ -36,146 +37,34 @@ class payment_line(orm.Model): _inherit = 'payment.line' _columns = { # New fields - 'export_state': fields.selection([ - ('draft', 'Draft'), - ('open','Confirmed'), - ('cancel','Cancelled'), - ('sent', 'Sent'), - ('rejected', 'Rejected'), - ('done','Done'), - ], 'State', select=True - ), 'msg': fields.char('Message', size=255, required=False, readonly=True), - - # Redefined fields: added states - 'date_done': fields.datetime('Date Confirmed', select=True, - readonly=True), - 'name': fields.char( - 'Your Reference', size=64, required=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), + 'date_done': fields.date( + 'Date Confirmed', select=True, readonly=True), + # Communication: required is dependend on the mode 'communication': fields.char( 'Communication', size=64, required=False, help=("Used as the message between ordering customer and current " "company. Depicts 'What do you want to say to the recipient" " about this order ?'" ), - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, ), + # Communication2: enlarge to 128 'communication2': fields.char( 'Communication 2', size=128, help='The successor message of Communication.', - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, ), - 'move_line_id': fields.many2one( - 'account.move.line', 'Entry line', - domain=[('reconcile_id','=', False), - ('account_id.type', '=','payable') - ], - help=('This Entry Line will be referred for the information of ' - 'the ordering customer.' - ), - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'amount_currency': fields.float( - 'Amount in Partner Currency', digits=(16,2), - required=True, - help='Payment amount in the partner currency', - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'currency': fields.many2one( - 'res.currency', 'Partner Currency', required=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'bank_id': fields.many2one( - 'res.partner.bank', 'Destination Bank account', - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'order_id': fields.many2one( - 'payment.order', 'Order', required=True, - ondelete='cascade', select=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'partner_id': fields.many2one( - 'res.partner', string="Partner", required=True, - help='The Ordering Customer', - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'date': fields.date( - 'Payment Date', - help=("If no payment date is specified, the bank will treat this " - "payment line directly" - ), - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'state': fields.selection([ - ('normal','Free'), - ('structured','Structured') - ], 'Communication Type', required=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - } + 'transit_move_line_id': fields.many2one( + # this line is part of the credit side of move 2a + # from the documentation + 'account.move.line', 'Debit move line', + readonly=True, + help="Move line through which the debit order pays the invoice", + ), + } + _defaults = { - 'export_state': 'draft', - 'date_done': False, 'msg': '', - } - - def fields_get(self, cr, uid, fields=None, context=None): - res = super(payment_line, self).fields_get(cr, uid, fields, context) - if 'communication' in res: - res['communication'].setdefault('states', {}) - res['communication']['states']['structured'] = [('required', True)] - if 'communication2' in res: - res['communication2'].setdefault('states', {}) - res['communication2']['states']['structured'] = [('readonly', True)] - res['communication2']['states']['normal'] = [('readonly', False)] - - return res + } """ Hooks for processing direct debit orders, such as implemented in @@ -216,3 +105,76 @@ class payment_line(orm.Model): """ return False + + def debit_reconcile(self, cr, uid, payment_line_id, context=None): + """ + Reconcile a debit order's payment line with the the move line + that it is based on. Called from payment_order.action_sent(). + As the amount is derived directly from the counterpart move line, + we do not expect a write off. Take partially reconcilions into + account though. + + :param payment_line_id: the single id of the canceled payment line + """ + + if isinstance(payment_line_id, (list, tuple)): + payment_line_id = payment_line_id[0] + reconcile_obj = self.pool.get('account.move.reconcile') + move_line_obj = self.pool.get('account.move.line') + payment_line = self.browse(cr, uid, payment_line_id, context=context) + + transit_move_line = payment_line.transit_move_line_id + torec_move_line = payment_line.move_line_id + + if (not transit_move_line or not torec_move_line): + raise orm.except_orm( + _('Can not reconcile'), + _('No move line for line %s') % payment_line.name) + if torec_move_line.reconcile_id: # torec_move_line.reconcile_partial_id: + raise orm.except_orm( + _('Error'), + _('Move line %s has already been reconciled') % + torec_move_line.name + ) + if transit_move_line.reconcile_id or transit_move_line.reconcile_partial_id: + raise orm.except_orm( + _('Error'), + _('Move line %s has already been reconciled') % + transit_move_line.name + ) + + def is_zero(total): + return self.pool.get('res.currency').is_zero( + cr, uid, transit_move_line.company_id.currency_id, total) + + line_ids = [transit_move_line.id, torec_move_line.id] + if torec_move_line.reconcile_partial_id: + line_ids = [ + x.id for x in + transit_move_line.reconcile_partial_id.line_partial_ids + ] + [torec_move_line.id] + + total = move_line_obj.get_balance(cr, uid, line_ids) + vals = { + 'type': 'auto', + 'line_id': is_zero(total) and [(6, 0, line_ids)] or [(6, 0, [])], + 'line_partial_ids': is_zero(total) and [(6, 0, [])] or [(6, 0, line_ids)], + } + + if torec_move_line.reconcile_partial_id: + reconcile_obj.write( + cr, uid, transit_move_line.reconcile_partial_id.id, + vals, context=context) + else: + reconcile_obj.create( + cr, uid, vals, context=context) + for line_id in line_ids: + netsvc.LocalService("workflow").trg_trigger( + uid, 'account.move.line', line_id, cr) + + # If a bank transaction of a storno was first confirmed + # and now canceled (the invoice is now in state 'debit_denied' + if torec_move_line.invoice: + netsvc.LocalService("workflow").trg_validate( + uid, 'account.invoice', torec_move_line.invoice.id, + 'undo_debit_denied', cr) diff --git a/account_banking_payment/model/payment_mode.py b/account_banking_payment/model/payment_mode.py index a250afa40..f93acec8e 100644 --- a/account_banking_payment/model/payment_mode.py +++ b/account_banking_payment/model/payment_mode.py @@ -46,6 +46,27 @@ class payment_mode(orm.Model): _columns = { 'type': fields.many2one( 'payment.mode.type', 'Payment type', + required=True, help='Select the Payment Type for the Payment Mode.' ), + 'transfer_account_id': fields.many2one( + 'account.account', 'Transfer account', + domain=[('type', '=', 'other'), + ('reconcile', '=', True)], + help=('Pay off lines in sent orders with a ' + 'move on this account. For debit type modes only. ' + 'You can only select accounts of type regular that ' + 'are marked for reconciliation'), + ), + 'transfer_journal_id': fields.many2one( + 'account.journal', 'Transfer journal', + help=('Journal to write payment entries when confirming ' + 'a debit order of this mode'), + ), + 'payment_term_ids': fields.many2many( + 'account.payment.term', 'account_payment_order_terms_rel', + 'mode_id', 'term_id', 'Payment terms', + help=('Limit selected invoices to invoices with these payment ' + 'terms') + ), } diff --git a/account_banking_payment/model/payment_order_create.py b/account_banking_payment/model/payment_order_create.py index 36c0f3aeb..68a0d22f4 100644 --- a/account_banking_payment/model/payment_order_create.py +++ b/account_banking_payment/model/payment_order_create.py @@ -26,11 +26,77 @@ from datetime import datetime from openerp.osv import orm, fields from openerp.tools import DEFAULT_SERVER_DATE_FORMAT +from openerp.tools.translate import _ class payment_order_create(orm.TransientModel): _inherit = 'payment.order.create' + def extend_payment_order_domain( + self, cr, uid, payment_order, domain, context=None): + if payment_order.payment_order_type == 'payment': + domain += [ + ('account_id.type', '=', 'payable'), + ('amount_to_pay', '>', 0) + ] + return True + + def search_entries(self, cr, uid, ids, context=None): + """ + This method taken from account_payment module. + We adapt the domain based on the payment_order_type + """ + line_obj = self.pool.get('account.move.line') + mod_obj = self.pool.get('ir.model.data') + if context is None: + context = {} + data = self.read(cr, uid, ids, ['duedate'], context=context)[0] + search_due_date = data['duedate'] + + ### start account_banking_payment ### + payment = self.pool.get('payment.order').browse( + cr, uid, context['active_id'], context=context) + # Search for move line to pay: + domain = [ + ('move_id.state', '=', 'posted'), + ('reconcile_id', '=', False), + ('company_id', '=', payment.mode.company_id.id), + ] + # apply payment term filter + if payment.mode.payment_term_ids: + domain += [ + ('invoice.payment_term', 'in', + [term.id for term in payment.mode.payment_term_ids] + ) + ] + self.extend_payment_order_domain( + cr, uid, payment, domain, context=context) + ### end account_direct_debit ### + + domain = domain + [ + '|', ('date_maturity', '<=', search_due_date), + ('date_maturity', '=', False) + ] + line_ids = line_obj.search(cr, uid, domain, context=context) + context.update({'line_ids': line_ids}) + model_data_ids = mod_obj.search( + cr, uid,[ + ('model', '=', 'ir.ui.view'), + ('name', '=', 'view_create_payment_order_lines')], + context=context) + resource_id = mod_obj.read( + cr, uid, model_data_ids, fields=['res_id'], + context=context)[0]['res_id'] + return {'name': _('Entry Lines'), + 'context': context, + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'payment.order.create', + 'views': [(resource_id, 'form')], + 'type': 'ir.actions.act_window', + 'target': 'new', + } + def create_payment(self, cr, uid, ids, context=None): ''' This method is a slightly modified version of the existing method on this @@ -51,7 +117,8 @@ class payment_order_create(orm.TransientModel): if not line_ids: return {'type': 'ir.actions.act_window_close'} - payment = order_obj.browse(cr, uid, context['active_id'], context=context) + payment = order_obj.browse( + cr, uid, context['active_id'], context=context) ### account banking # t = None # line2bank = line_obj.line2bank(cr, uid, line_ids, t, context) @@ -75,10 +142,10 @@ class payment_order_create(orm.TransientModel): ### end account banking elif payment.date_prefered == 'fixed': ### account_banking - # date_to_pay = payment.date_planned + # date_to_pay = payment.date_scheduled date_to_pay = ( - payment.date_planned - if payment.date_planned and payment.date_planned > _today + payment.date_scheduled + if payment.date_scheduled and payment.date_scheduled > _today else False) ### end account banking @@ -125,6 +192,8 @@ class payment_order_create(orm.TransientModel): 'state': state, ### end account banking 'date': date_to_pay, - 'currency': line.invoice and line.invoice.currency_id.id or line.journal_id.currency.id or line.journal_id.company_id.currency_id.id, + 'currency': (line.invoice and line.invoice.currency_id.id + or line.journal_id.currency.id + or line.journal_id.company_id.currency_id.id), }, context=context) return {'type': 'ir.actions.act_window_close'} diff --git a/account_banking_payment/view/account_payment.xml b/account_banking_payment/view/account_payment.xml index a88594e1f..4f7601332 100644 --- a/account_banking_payment/view/account_payment.xml +++ b/account_banking_payment/view/account_payment.xml @@ -9,33 +9,28 @@ account.payment.order.form.banking-1 payment.order - form - - {'invisible':[('state','!=','draft')]} + + { + 'invisible':[('state','!=','draft')] + } - - - - - diff --git a/account_banking_payment/view/bank_payment_manual.xml b/account_banking_payment/view/bank_payment_manual.xml index 64e8bd08a..538862ca3 100644 --- a/account_banking_payment/view/bank_payment_manual.xml +++ b/account_banking_payment/view/bank_payment_manual.xml @@ -4,9 +4,8 @@ Form for manual payment wizard payment.manual - form - +