Exemplo n.º 1
0
        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
Exemplo n.º 2
0
 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)
Exemplo n.º 5
0
 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
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
    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"))
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
    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,
        }