def extended_filter_term(term): ''' Extend the search criteria in term when appropriate. ''' result = [term] extra_terms = [] if term[0].lower() == 'acc_number' and term[1] in ('=', '=='): iban = sepa.IBAN(term[2]) if iban.valid: # Disregard spaces when comparing IBANs cr.execute( """ SELECT id FROM res_partner_bank WHERE replace(acc_number, ' ', '') = %s """, (term[2].replace(' ', ''),)) ids = [row[0] for row in cr.fetchall()] result = [('id', 'in', ids)] if 'acc_number_domestic' in self._columns: bban = iban.localized_BBAN # Prevent empty search filters if bban: extra_terms.append( ('acc_number_domestic', term[1], bban)) for extra_term in extra_terms: result = ['|'] + result + [extra_term] return result
def _correct_IBAN(acc_number): ''' Routine to correct IBAN values and deduce localized values when valid. Note: No check on validity IBAN/Country ''' iban = sepa.IBAN(acc_number) return (str(iban), iban.localized_BBAN)
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 """ country_code = False iban = sepa.IBAN(transaction.remote_account) if iban.valid: country_code = iban.countrycode elif transaction.remote_owner_country_code: country_code = transaction.remote_owner_country_code # fallback on the import parsers country code elif transaction.bank_country_code: country_code = transaction.bank_country_code if country_code: country_ids = pool.get('res.country').search( cr, uid, [('code', '=', country_code.upper())], context=context) country_id = country_ids and country_ids[0] or False if not country_id: company = transaction.statement_line_id.company_id if company.partner_id.country: 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, context=None): ''' 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, ) # Are we dealing with IBAN? iban = sepa.IBAN(account_number) if iban.valid: # Take as much info as possible from IBAN values.state = 'iban' values.acc_number = str(iban) else: # No, try to convert to IBAN values.state = 'bank' values.acc_number = account_number if country_id: country_code = pool.get('res.country').read( cr, uid, country_id, ['code'], context=context)['code'] if country_code in sepa.IBAN.countries: account_info = pool['res.partner.bank'].online_account_info( cr, uid, country_code, values.acc_number, context=context) if account_info: values.acc_number = iban = account_info.iban values.state = 'iban' bic = account_info.bic if bic: values.bank = get_or_create_bank(pool, cr, uid, bic)[0] values.bank_bic = bic # Create bank account and return return pool.get('res.partner.bank').create(cr, uid, values, context=context)
def extended_filter_term(term): ''' Extend the search criteria in term when appropriate. ''' result = [term] extra_terms = [] if term[0].lower() == 'acc_number' and term[1] in ('=', '=='): iban = sepa.IBAN(term[2]) if iban.valid: # Disregard spaces when comparing IBANs cr.execute( """ SELECT id FROM res_partner_bank WHERE replace(acc_number, ' ', '') = %s """, (term[2].replace(' ', ''), )) ids = [row[0] for row in cr.fetchall()] result = [('id', 'in', ids)] for extra_term in extra_terms: result = ['|'] + result + [extra_term] return result
def init(self, cr): ''' Update existing iban accounts to comply to new regime ''' partner_bank_obj = self.pool.get('res.partner.bank') bank_ids = partner_bank_obj.search(cr, SUPERUSER_ID, [('state', '=', 'iban')], limit=0) for bank in partner_bank_obj.read(cr, SUPERUSER_ID, bank_ids): write_vals = {} if bank['state'] == 'iban': iban_acc = sepa.IBAN(bank['acc_number']) if iban_acc.valid: write_vals['acc_number_domestic'] = iban_acc.localized_BBAN write_vals['acc_number'] = str(iban_acc) elif bank['acc_number'] != bank['acc_number'].upper(): write_vals['acc_number'] = bank['acc_number'].upper() if write_vals: partner_bank_obj.write(cr, SUPERUSER_ID, bank['id'], write_vals)
def onchange_iban(self, cr, uid, ids, acc_number, acc_number_domestic, state, partner_id, country_id, context=None): ''' Trigger to verify IBAN. When valid: 1. Extract BBAN as local account 2. Auto complete bank ''' if not acc_number: return {} iban_acc = sepa.IBAN(acc_number) if iban_acc.valid: bank_id, country_id = get_or_create_bank( self.pool, cr, uid, iban_acc.BIC_searchkey, code=iban_acc.BIC_searchkey) return { 'value': dict( acc_number_domestic=iban_acc.localized_BBAN, acc_number=unicode(iban_acc), country=country_id or False, bank=bank_id or False, ) } return warning(_('Invalid IBAN account number!'), _("The IBAN number doesn't seem to be correct"))
def lookup_all(self, cr, uid, ids, context=None): partner_bank_obj = self.pool.get('res.partner.bank') bank_obj = self.pool.get('res.bank') bank_cache = {} def get_bank(bic): """ Return browse object of bank by bic """ if not bank_cache.get(bic): bank_id, _country = get_or_create_bank( self.pool, cr, uid, bic) bank_cache[bic] = bank_obj.browse( cr, uid, bank_id, context=context) return bank_cache[bic] def repr(iban): parts = [] for i in range(0, len(iban), 4): parts.append(iban[i:i+4]) return ' '.join(parts) # Get existing IBANs partner_ids = self.get_nl_partner_ids(cr, uid, context=context) partner_bank_ids = partner_bank_obj.search( cr, uid, [('state', '=', 'iban'), ('acc_number_domestic', '!=', False), ('acc_number_domestic', '!=', ''), '|', '&', ('country_id', '=', False), ('partner_id', 'in', partner_ids), ('country_id.code', '=', 'NL')], context=context) for account in partner_bank_obj.browse( cr, uid, partner_bank_ids, context=context): res = iban_lookup(account.acc_number_domestic) if not res: logger.warn( 'Error getting IBAN for %s (%s)', account.acc_number_domestic, account.acc_number) continue logger.debug( 'Lookup of %s (%s): %s (%s)', account.acc_number_domestic, account.acc_number, res.iban, res.bic) iban = repr(res.iban) if iban != account.acc_number: logger.info( 'Replacing IBAN %s by %s', account.acc_number, iban) bank = get_bank(res.bic) account.write({ 'bank': bank.id, 'bank_name': bank.name, 'bank_bic': res.bic, 'acc_number': iban, }) # Now get regular accounts partner_bank_ids = partner_bank_obj.search( cr, uid, [('state', '=', 'bank'), '|', '&', ('country_id', '=', False), ('partner_id', 'in', partner_ids), ('country_id.code', '=', 'NL')], context=context) for account in partner_bank_obj.browse( cr, uid, partner_bank_ids, context=context): values = {} try: info = online.account_info('NL', account.acc_number) if info: iban_acc = sepa.IBAN(info.iban) if iban_acc.valid: bank = get_bank(info.bic) values = { 'acc_number_domestic': iban_acc.localized_BBAN, 'acc_number': unicode(iban_acc), 'state': 'iban', 'bank': bank.id, 'bank_bic': info.bic, 'bank_name': bank.name, } account.write(values) else: logger.warn( 'IBAN for %s not valid: %s', account.acc_number, info.iban) else: logger.warn( 'Error getting IBAN for %s', account.acc_number) except Exception, e: logger.warn( 'Error getting IBAN for %s: %s', account.acc_number, e)
def onchange_domestic(self, cr, uid, ids, acc_number, partner_id, country_id, context=None): ''' Trigger to find IBAN. When found: 1. Reformat BBAN 2. Autocomplete bank TODO: prevent unnecessary assignment of country_ids and browsing of the country ''' if not acc_number: return {} values = {} country_obj = self.pool.get('res.country') country_ids = [] country = False # Pre fill country based on available data. This is just a default # which can be overridden by the user. # 1. Use provided country_id (manually filled) if 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 # addresses, but there was a country set in the address for the bank # 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(cr, uid, ids[0], context=context) if partner_bank_id.country_id: country = partner_bank_id.country_id country_ids = [country.id] # 3. Use country_id of default address of partner # The country_id of a bank account is a one time default on creation. # It originates in the same address we are about to check, but # modifications on that address afterwards are not transfered to the # bank account, hence the additional check. elif partner_id: partner_obj = self.pool.get('res.partner') 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(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 user.company_id.partner_id.country): country_ids = [user.company_id.partner_id.country.id] else: if (user.company_id and user.company_id.partner_id and user.company_id.partner_id.country): country_ids = [user.company_id.partner_id.country.id] else: # Ok, tried everything, give up and leave it to the user return warning( _('Insufficient data'), _('Insufficient data to select online ' 'conversion database')) result = {'value': values} # Complete data with online database when available if country_ids: country = country_obj.browse(cr, uid, country_ids[0], context=context) values['country_id'] = country_ids[0] if country and country.code in sepa.IBAN.countries: info = online.account_info(country.code, acc_number) if info: iban_acc = sepa.IBAN(info.iban) if iban_acc.valid: values['acc_number_domestic'] = iban_acc.localized_BBAN values['acc_number'] = unicode(iban_acc) values['state'] = 'iban' bank_id, country_id = get_or_create_bank( self.pool, cr, uid, info.bic or iban_acc.BIC_searchkey, name=info.bank) if country_id: values['country_id'] = country_id values['bank'] = bank_id or False if info.bic: values['bank_bic'] = info.bic else: info = None if info is None: result.update( warning( _('Invalid data'), _('The account number appears to be invalid for %s') % country.name)) if info is False: if country.code in sepa.IBAN.countries: acc_number_fmt = sepa.BBAN(acc_number, country.code) if acc_number_fmt.valid: values['acc_number_domestic'] = str(acc_number_fmt) else: result.update( warning( _('Invalid format'), _('The account number has the wrong format for %s' ) % country.name)) return result
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(cr, uid, ids, context)[0] clieopfile = None for payment_order in clieop_export.payment_order_ids: if not clieopfile: # Just once: create clieop file our_account_owner = payment_order.mode.bank_id.owner_name \ or payment_order.mode.bank_id.partner_id.name if payment_order.mode.bank_id.state == 'iban': our_account_nr = payment_order.mode.bank_id.acc_number_domestic if not our_account_nr: our_account_nr = sepa.IBAN( payment_order.mode.bank_id.acc_number ).localized_BBAN else: our_account_nr = payment_order.mode.bank_id.acc_number if not our_account_nr: raise orm.except_orm( _('Error'), _('Your bank account has to have a valid account number') ) clieopfile = {'CLIEOPPAY': clieop.PaymentsFile, 'CLIEOPINC': clieop.DirectDebitFile, 'CLIEOPSAL': clieop.SalaryPaymentsFile, }[clieop_export['batchtype']]( identification = clieop_export['reference'], execution_date = clieop_export['execution_date'], name_sender = our_account_owner, accountno_sender = our_account_nr, seqno = self.pool.get( 'banking.export.clieop').get_daynr( cr, uid, context=context), test = clieop_export['test'] ) # ClieOp3 files can contain multiple batches, but we put all # orders into one single batch. Ratio behind this is that a # batch costs more money than a single transaction, so it is # cheaper to combine than it is to split. As we split out all # reported errors afterwards, there is no additional gain in # using multiple batches. if clieop_export['fixed_message']: messages = [clieop_export['fixed_message']] else: messages = [] # The first payment order processed sets the reference of the # batch. batch = clieopfile.batch( messages = messages, batch_id = clieop_export['reference'] ) 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 orm.except_orm( _('Error'), _('There is insufficient information.\r\n' 'Both destination address and account ' 'number must be provided' ) ) kwargs = dict( name = line.bank_id.owner_name or line.bank_id.partner_id.name, amount = line.amount_currency, reference = line.communication or None, ) if line.communication2: kwargs['messages'] = [line.communication2] other_account_nr = ( line.bank_id.state == 'iban' and line.bank_id.acc_number_domestic or line.bank_id.acc_number ) iban = sepa.IBAN(other_account_nr) # Is this an IBAN account? if iban.valid: if iban.countrycode != 'NL': raise orm.except_orm( _('Error'), _('You cannot send international bank transfers ' 'through ClieOp3!') ) other_account_nr = iban.localized_BBAN if clieop_export['batchtype'] == 'CLIEOPINC': kwargs['accountno_beneficiary'] = our_account_nr kwargs['accountno_payer'] = other_account_nr else: kwargs['accountno_beneficiary'] = other_account_nr kwargs['accountno_payer'] = our_account_nr transaction = batch.transaction(**kwargs) # Generate the specifics of this clieopfile order = clieopfile.order file_id = self.pool.get('banking.export.clieop').create( cr, uid, dict( filetype = order.name_transactioncode, identification = order.identification, prefered_date = strfdate(order.preferred_execution_date), total_amount = int(order.total_amount) / 100.0, check_no_accounts = order.total_accountnos, no_transactions = order.nr_posts, testcode = order.testcode, file = base64.encodestring(clieopfile.rawdata), filename = 'Clieop03-{0}.txt'.format(order.identification), daynumber = int(clieopfile.header.file_id[2:]), payment_order_ids = [ [6, 0, [x.id for x in clieop_export['payment_order_ids']]] ], ), context) self.write(cr, uid, [ids[0]], dict( filetype = order.name_transactioncode, testcode = order.testcode, file_id = file_id, state = 'finish', ), context) return { 'name': _('Client Opdrachten Export'), 'view_type': 'form', 'view_mode': 'form', 'res_model': self._name, 'domain': [], 'context': dict(context, active_ids=ids), 'type': 'ir.actions.act_window', 'target': 'new', 'res_id': ids[0] or False, }