def get_company_bank_account(pool, cursor, uid, account_number, company, log):
    '''
    Get the matching bank account for this company.
    '''
    results = struct()
    bank_account = get_bank_account(pool,
                                    cursor,
                                    uid,
                                    account_number,
                                    log,
                                    fail=True)
    if not bank_account:
        return False
    if bank_account.partner_id.id != company.partner_id.id:
        log.append(
            _('Account %(account_no)s is not owned by %(partner)s') % dict(
                account_no=account_number,
                partner=company.partner_id.name,
            ))
        return False
    results.account = bank_account
    bank_settings_obj = pool.get('account.banking.account.settings')
    bank_settings_ids = bank_settings_obj.search(
        cursor, uid, [('partner_bank_id', '=', bank_account.id)])
    if bank_settings_ids:
        settings = bank_settings_obj.browse(cursor, uid, bank_settings_ids)[0]
        results.journal_id = settings.journal_id
        results.default_debit_account_id = settings.default_debit_account_id
        results.default_credit_account_id = settings.default_credit_account_id
    return results
Exemple #2
0
def get_company_bank_account(pool, cursor, uid, account_number,
                             company, log):
    '''
    Get the matching bank account for this company.
    '''
    results = struct()
    bank_account = get_bank_account(pool, cursor, uid, account_number, log,
                                    fail=True)
    if not bank_account:
        return False
    if bank_account.partner_id.id != company.partner_id.id:
        log.append(
            _('Account %(account_no)s is not owned by %(partner)s')
            % dict(account_no = account_number,
                   partner = company.partner_id.name,
        ))
        return False
    results.account = bank_account
    bank_settings_obj = pool.get('account.banking.account.settings')
    bank_settings_ids = bank_settings_obj.search(cursor, uid, [
        ('partner_bank_id', '=', bank_account.id)
    ])
    if bank_settings_ids:
        settings = bank_settings_obj.browse(cursor, uid, bank_settings_ids)[0]
        results.journal_id = settings.journal_id
        results.default_debit_account_id = settings.default_debit_account_id
        results.default_credit_account_id = settings.default_credit_account_id
    return results
Exemple #3
0
def get_iban_bic_NL(bank_acc):
    '''
    Consult the Dutch online banking database to check both the account number
    and the bank to which it belongs. Will not work offline, is limited to
    banks operating in the Netherlands and will only convert Dutch local
    account numbers.
    '''
    data = urllib.urlencode(dict(number=bank_acc, method='POST'))
    request = urllib2.Request(IBANlink_NL, data)
    response = urllib2.urlopen(request)
    soup = BeautifulSoup(response)
    result = struct()
    for _pass, td in enumerate(soup.findAll('td')):
        if _pass % 2 == 1:
            result[attr] = unicode(td.find('font').contents[0])
        else:
            attr = td.find('strong').contents[0][:4].strip().lower()
    if result:
        result.account = bank_acc
        result.country_id = result.bic[4:6]
        # Nationalized bank code
        result.code = result.bic[:6]
        # All Dutch banks use generic channels
        # result.bic += 'XXX'
        return result
    return None
Exemple #4
0
def get_iban_bic_NL(bank_acc):
    '''
    Consult the Dutch online banking database to check both the account number
    and the bank to which it belongs. Will not work offline, is limited to
    banks operating in the Netherlands and will only convert Dutch local
    account numbers.
    '''
    data = urllib.urlencode(dict(number=bank_acc, method='POST'))
    request = urllib2.Request(IBANlink_NL, data)
    response = urllib2.urlopen(request)
    soup = BeautifulSoup(response)
    result = struct()
    for _pass, td in enumerate(soup.findAll('td')):
        if _pass % 2 == 1:
            result[attr] = unicode(td.find('font').contents[0])
        else:
            attr = td.find('strong').contents[0][:4].strip().lower()
    if result:
        result.account = bank_acc
        result.country_id = result.bic[4:6]
        # Nationalized bank code
        result.code = result.bic[:6]
        # All Dutch banks use generic channels
        # result.bic += 'XXX'
        return result
    return None
Exemple #5
0
def get_company_bank_account(pool, cursor, 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,
                                      fail=True)
    if not bank_accounts:
        return False
    elif len(bank_accounts) != 1:
        log.append(
            _('More than one bank account was found with the same number %(account_no)s')
            % dict(account_no = account_number)
        )
        return False
    if bank_accounts[0].partner_id.id != company.partner_id.id:
        log.append(
            _('Account %(account_no)s is not owned by %(partner)s')
            % dict(account_no = account_number,
                   partner = company.partner_id.name,
        ))
        return False
    results.account = bank_accounts[0]
    bank_settings_obj = pool.get('account.banking.account.settings')
    criteria = [('partner_bank_id', '=', bank_accounts[0].id)]

    # Find matching journal for currency
    journal_obj = pool.get('account.journal')
    journal_ids = journal_obj.search(cursor, uid, [
        ('type', '=', 'bank'),
        ('currency', '=', currency or company.currency_id.name)
    ])
    if not journal_ids and currency == company.currency_id.name:
        journal_ids = journal_obj.search(cursor, uid, [
            ('type', '=', 'bank'), ('currency', '=', False)
        ])
    if journal_ids:
        criteria.append(('journal_id', 'in', journal_ids))

    # Find bank account settings
    bank_settings_ids = bank_settings_obj.search(cursor, uid, criteria)
    if bank_settings_ids:
        settings = bank_settings_obj.browse(cursor, uid, bank_settings_ids)[0]
        results.company_id = company
        results.journal_id = settings.journal_id
        # Take currency from settings or from company
        if settings.journal_id.currency.id:
            results.currency_id = settings.journal_id.currency
        else:
            results.currency_id = company.currency_id
        # Rest just copy/paste from settings. Why am I doing this?
        results.default_debit_account_id = settings.default_debit_account_id
        results.default_credit_account_id = settings.default_credit_account_id
        results.costs_account_id = settings.costs_account_id
        results.invoice_journal_id = settings.invoice_journal_id
        results.bank_partner_id = settings.bank_partner_id
    return results
Exemple #6
0
def get_iban_bic_NL(bank_acc):
    '''
    Consult the Dutch online banking database to check both the account number
    and the bank to which it belongs. Will not work offline, is limited to
    banks operating in the Netherlands and will only convert Dutch local
    account numbers.
    '''
    # sanity check: Dutch 7 scheme uses ING as sink and online convertor
    # calculates accounts, so no need to consult it - calculate our own
    number = bank_acc.lstrip('0')
    if len(number) <= 7:
        iban = IBAN.create(BBAN='INGB' + number.rjust(10, '0'),
                           countrycode='NL'
                          )
        return struct(
            iban = iban.replace(' ',''),
            account = iban.BBAN[4:],
            bic = 'INGBNL2A',
            code = 'INGBNL',
            bank = 'ING Bank N.V.',
            country_id = 'NL',
        )

    data = urllib.urlencode(dict(number=number, method='POST'))
    request = urllib2.Request(IBANlink_NL, data)
    response = urllib2.urlopen(request)
    soup = BeautifulSoup(response)
    result = struct()
    for _pass, td in enumerate(soup.findAll('td')):
        if _pass % 2 == 1:
            result[attr] = unicode(td.find('font').contents[0])
        else:
            attr = td.find('strong').contents[0][:4].strip().lower()
    if result:
        result.account = bank_acc
        result.country_id = result.bic[4:6]
        # Nationalized bank code
        result.code = result.bic[:6]
        # All Dutch banks use generic channels
        # result.bic += 'XXX'
        return result
    return None
Exemple #7
0
def get_iban_bic_NL(bank_acc):
    '''
    Consult the Dutch online banking database to check both the account number
    and the bank to which it belongs. Will not work offline, is limited to
    banks operating in the Netherlands and will only convert Dutch local
    account numbers.
    '''
    # sanity check: Dutch 7 scheme uses ING as sink and online convertor
    # calculates accounts, so no need to consult it - calculate our own
    number = bank_acc.lstrip('0')
    if len(number) <= 7:
        iban = IBAN.create(BBAN='INGB' + number.rjust(10, '0'),
                           countrycode='NL')
        return struct(
            iban=iban.replace(' ', ''),
            account=iban.BBAN[4:],
            bic='INGBNL2A',
            code='INGBNL',
            bank='ING Bank N.V.',
            country_id='NL',
        )

    data = urllib.urlencode(dict(number=number, method='POST'))
    request = urllib2.Request(IBANlink_NL, data)
    response = urllib2.urlopen(request)
    soup = BeautifulSoup(response)
    result = struct()
    for _pass, td in enumerate(soup.findAll('td')):
        if _pass % 2 == 1:
            result[attr] = unicode(td.find('font').contents[0])
        else:
            attr = td.find('strong').contents[0][:4].strip().lower()
    if result:
        result.account = bank_acc
        result.country_id = result.bic[4:6]
        # Nationalized bank code
        result.code = result.bic[:6]
        # All Dutch banks use generic channels
        # result.bic += 'XXX'
        return result
    return None
Exemple #8
0
 def harvest(soup):
     retval = struct()
     for trsoup in soup('tr'):
         for stage, tdsoup in enumerate(trsoup('td')):
             if stage == 0:
                 attr = tdsoup.contents[0].strip().replace(' ', '_')
             elif stage == 2:
                 if tdsoup.contents:
                     retval[attr] = tdsoup.contents[0].strip()
                 else:
                     retval[attr] = ''
     return retval
Exemple #9
0
 def harvest(soup):
     retval = struct()
     for trsoup in soup('tr'):
         for stage, tdsoup in enumerate(trsoup('td')):
             if stage == 0:
                 attr = tdsoup.contents[0].strip().replace(' ','_')
             elif stage == 2:
                 if tdsoup.contents:
                     retval[attr] = tdsoup.contents[0].strip()
                 else:
                     retval[attr] = ''
     return retval
    def _get_move_info(self,
                       cursor,
                       uid,
                       move_line,
                       partner_bank_id=False,
                       partial=False):
        reconcile_obj = self.pool.get('account.move.reconcile')
        type_map = {
            'out_invoice': 'customer',
            'in_invoice': 'supplier',
            'out_refund': 'customer',
            'in_refund': 'supplier',
        }
        retval = struct(move_line=move_line,
                        partner_id=move_line.partner_id.id,
                        partner_bank_id=partner_bank_id,
                        reference=move_line.ref)
        if move_line.invoice:
            retval.invoice = move_line.invoice
            retval.type = type_map[move_line.invoice.type]
        else:
            retval.type = 'general'

        if partial:
            move_line.reconcile_partial_id = reconcile_obj.create(
                cursor, uid, {
                    'type': 'auto',
                    'line_partial_ids': [(4, 0, [move_line.id])]
                })
        else:
            if move_line.reconcile_partial_id:
                partial_ids = [
                    x.id
                    for x in move_line.reconcile_partial_id.line_partial_ids
                ]
            else:
                partial_ids = []
            move_line.reconcile_id = reconcile_obj.create(
                cursor, uid, {
                    'type':
                    'auto',
                    'line_id': [(4, x, False)
                                for x in [move_line.id] + partial_ids],
                    'line_partial_ids': [(3, x, False) for x in partial_ids]
                })
        return retval
Exemple #11
0
    def _get_move_info(self, cursor, uid, move_line, partner_bank_id=False,
                       partial=False):
        reconcile_obj = self.pool.get('account.move.reconcile')
        type_map = {
            'out_invoice': 'customer',
            'in_invoice': 'supplier',
            'out_refund': 'customer',
            'in_refund': 'supplier',
        }
        retval = struct(move_line=move_line, partner_id=move_line.partner_id.id,
                        partner_bank_id=partner_bank_id,
                        reference=move_line.ref
                       )
        if move_line.invoice:
            retval.invoice = move_line.invoice
            retval.type = type_map[move_line.invoice.type]
        else:
            retval.type = 'general'

        if partial:
            move_line.reconcile_partial_id = reconcile_obj.create(
                cursor, uid, {
                    'type': 'auto',
                    'line_partial_ids': [(4, 0, [move_line.id])]
                    }
            )
        else:
            if move_line.reconcile_partial_id:
                partial_ids = [x.id for x in
                               move_line.reconcile_partial_id.line_partial_ids
                              ]
            else:
                partial_ids = []
            move_line.reconcile_id = reconcile_obj.create(
                cursor, uid, {
                    'type': 'auto',
                    'line_id': [
                        (4, x, False) for x in [move_line.id] + partial_ids
                    ],
                    'line_partial_ids': [
                        (3, x, False) for x in partial_ids
                    ]
                }
            )
        return retval
Exemple #12
0
def BBAN_is_IBAN(bank_acc):
    '''
    Intelligent copy, valid for SEPA members who switched to SEPA from old
    standards before SEPA actually started.
    '''
    if isinstance(bank_acc, IBAN):
        iban_acc = bank_acc
    else:
        iban_acc = IBAN(bank_acc)
    return struct(
        iban=str(iban_acc),
        account=str(bank_acc),
        country_id=iban_acc.countrycode,
        code=iban_acc.BIC_searchkey,
        # Note: BIC can not be constructed here!
        bic=False,
        bank=False,
    )
Exemple #13
0
def BBAN_is_IBAN(bank_acc):
    '''
    Intelligent copy, valid for SEPA members who switched to SEPA from old
    standards before SEPA actually started.
    '''
    if isinstance(bank_acc, IBAN):
        iban_acc = bank_acc
    else:
        iban_acc = IBAN(bank_acc)
    return struct(
        iban = str(iban_acc),
        account = str(bank_acc),
        country_id = iban_acc.countrycode,
        code = iban_acc.BIC_searchkey,
        # Note: BIC can not be constructed here!
        bic = False,
        bank = False,
    )
Exemple #14
0
def get_iban_bic_BE(bank_acc):
    '''
    Consult the Belgian online database to check both account number and the
    bank it belongs to. Will not work offline, is limited to banks operating
    in Belgium and will only convert Belgian local account numbers.
    '''
    def contents(soup, attr):
        return soup.find('input', {
            'id': 'textbox%s' % attr
        }).get('value').strip()

    if not bank_acc.strip():
        return None

    # Get empty form with hidden validators
    agent = URLAgent()
    request = agent.open(IBANlink_BE)

    # Isolate form and fill it in
    soup = BeautifulSoup(request)
    form = SoupForm(soup.find('form', {'id': 'form1'}))
    form['textboxBBAN'] = bank_acc.strip()
    form['Convert'] = 'Convert Number'

    # Submit the form
    response = agent.submit(form)

    # Parse the results
    soup = BeautifulSoup(response)
    iban = contents(soup, 'IBAN')
    if iban.lower().startswith('not a'):
        return None
    result = struct(iban=iban.replace(' ', ''))
    result.bic = contents(soup, 'BIC').replace(' ', '')
    result.bank = contents(soup, 'BankName')

    # Add substracts
    result.account = bank_acc
    result.country_id = result.bic[4:6]
    result.code = result.bic[:6]
    return result
Exemple #15
0
def get_iban_bic_BE(bank_acc):
    '''
    Consult the Belgian online database to check both account number and the
    bank it belongs to. Will not work offline, is limited to banks operating
    in Belgium and will only convert Belgian local account numbers.
    '''
    def contents(soup, attr):
        return soup.find('input', {'id': 'textbox%s' % attr}).get('value').strip()

    if not bank_acc.strip():
        return None

    # Get empty form with hidden validators
    agent = URLAgent()
    request = agent.open(IBANlink_BE)

    # Isolate form and fill it in
    soup = BeautifulSoup(request)
    form = SoupForm(soup.find('form', {'id': 'form1'}))
    form['textboxBBAN'] = bank_acc.strip()
    form['Convert'] = 'Convert Number'

    # Submit the form
    response = agent.submit(form)

    # Parse the results
    soup = BeautifulSoup(response)
    iban = contents(soup, 'IBAN')
    if iban.lower().startswith('not a'):
        return None
    result = struct(iban=iban.replace(' ', ''))
    result.bic = contents(soup, 'BIC').replace(' ', '')
    result.bank = contents(soup, 'BankName')

    # Add substracts
    result.account = bank_acc
    result.country_id = result.bic[4:6]
    result.code = result.bic[:6]
    return result
Exemple #16
0
def bank_info(bic):
    '''
    Consult the free online SWIFT service to obtain the name and address of a
    bank. This call may take several seconds to complete, due to the number of
    requests to make. In total three HTTP requests are made per function call.
    In theory one request could be stripped, but the SWIFT terms of use prevent
    automated usage, so user like behavior is required.
    '''
    def harvest(soup):
        retval = struct()
        for trsoup in soup('tr'):
            for stage, tdsoup in enumerate(trsoup('td')):
                if stage == 0:
                    attr = tdsoup.contents[0].strip().replace(' ', '_')
                elif stage == 2:
                    if tdsoup.contents:
                        retval[attr] = tdsoup.contents[0].strip()
                    else:
                        retval[attr] = ''
        return retval

    # Get form
    agent = URLAgent()
    request = agent.open(SWIFTlink)
    soup = BeautifulSoup(request)

    # Parse request form. As this form is intertwined with a table, use the parent
    # as root to search for form elements.
    form = SoupForm(soup.find('form', {'id': 'frmFreeSearch1'}), parent=True)

    # Fill form fields
    form['selected_bic'] = bic

    # Get intermediate response
    response = agent.submit(form)

    # Parse response
    soup = BeautifulSoup(response)

    # Isolate the full 11 BIC - there may be more, but we only use the first
    bic_button = soup.find('a', {'class': 'bigbuttonblack'})
    if not bic_button:
        return None, None

    # Overwrite the location with 'any' ('XXX') to narrow the results to one or less.
    # Assume this regexp will never fail...
    full_bic = bic_re.match(bic_button.get('href')).groups()[0][:8] + 'XXX'

    # Get the detail form
    form = SoupForm(soup.find('form', {'id': 'frmDetail'}))

    # Fill detail fields
    form['selected_bic11'] = full_bic

    # Get final response
    response = agent.submit(form)
    soup = BeautifulSoup(response)

    # Now parse the results
    tables = soup.find('div', {'id': 'Middle'}).findAll('table')
    if not tables:
        return None, None
    tablesoup = tables[2]('table')
    if not tablesoup:
        return None, None

    codes = harvest(tablesoup[0])
    if not codes:
        return None, None

    bankinfo = struct(
        # Most banks use the first four chars of the BIC as an identifier for
        # their 'virtual bank' accross the world, containing all national
        # banks world wide using the same name.
        # The concatenation with the two character country code is for most
        # national branches sufficient as a unique identifier.
        code=full_bic[:6],
        bic=full_bic,
        name=codes.Institution_name,
    )

    address = harvest(tablesoup[1])
    # The address in the SWIFT database includes a postal code.
    # We need to split it into two fields...
    if not address.Zip_Code:
        if address.Location:
            iso, address.Zip_Code, address.Location = \
                    postalcode.split(address.Location, full_bic[4:6])

    bankaddress = struct(
        street=address.Address.title(),
        city=address.Location.strip().title(),
        zip=address.Zip_Code,
        country=address.Country.title(),
        country_id=full_bic[4:6],
    )
    if '  ' in bankaddress.street:
        bankaddress.street, bankaddress.street2 = [
            x.strip() for x in bankaddress.street.split('  ', 1)
        ]
    else:
        bankaddress.street2 = ''

    return bankinfo, bankaddress
Exemple #17
0
def create_bank_account(pool, cursor, uid, partner_id,
                        account_number, holder_name, address, city,
                        country_code, log
                        ):
    '''
    Create a matching bank account with this holder for this partner.
    '''
    values = struct(
        partner_id = partner_id,
        owner_name = holder_name,
    )
    bankcode = None
    bic = None
    country_obj = pool.get('res.country')

    # 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.iban = str(iban)
        values.acc_number = iban.BBAN
        bankcode = iban.bankcode + iban.countrycode
        country_code = iban.countrycode

    if not country_code:
        country = pool.get('res.partner').browse(
            cursor, uid, partner_id).country
        country_code = country.code
        country_id = country.id
    else:
        if iban.valid:
            country_ids = country_obj.search(cursor, uid,
                                             [('code', '=', iban.countrycode)]
                                             )
        else:
            country_ids = country_obj.search(cursor, uid,
                                             [('code', '=', country_code)]
                                             )
        country_id = country_ids[0]
    
    account_info = False
    if not iban.valid:
        # No, try to convert to IBAN
        values.state = 'bank'
        values.acc_number = account_number
        if country_code in sepa.IBAN.countries:
            account_info = sepa.online.account_info(country_code,
                                                    values.acc_number
                                                   )
            if account_info:
                values.iban = iban = account_info.iban
                values.state = 'iban'
                bankcode = account_info.code
                bic = account_info.bic

    if bic:
        values.bank = get_or_create_bank(pool, cursor, uid, bic)[0]

    else:
        if not bankcode:
            bankcode = "UNKNOW"
        # Try to link bank
        bank_obj = pool.get('res.bank')
        bank_ids = bank_obj.search(cursor, uid, [
            ('code', 'ilike', bankcode)
        ])
        if bank_ids:
            # Check BIC on existing banks
            values.bank = bank_ids[0]
            bank = bank_obj.browse(cursor, uid, values.bank)
            if not bank.bic:
                bank_obj.write(cursor, uid, values.bank, dict(bic=bic))
        else:
            # New bank - create
            res = struct(country_id=country_id)
            if account_info:
                res.code = account_info.code
                # Only the first eight positions of BIC are used for bank
                # transfers, so ditch the rest.
                res.bic = account_info.bic[:8]
                res.name = account_info.bank
            else:
                res.code = bankcode
                res.name = _('Unknown Bank')

            values.bank = bank_obj.create(cursor, uid, res)

    # Create bank account and return
    return pool.get('res.partner.bank').create(cursor, uid, values)
Exemple #18
0
def create_bank_account(pool, cursor, uid, partner_id,
                        account_number, holder_name, log
                        ):
    '''
    Create a matching bank account with this holder for this partner.
    '''
    values = struct(
        partner_id = partner_id,
        owner_name = holder_name,
    )
    bankcode = None
    bic = None

    # Are we dealing with IBAN?
    iban = sepa.IBAN(account_number)
    if iban.valid:
        values.state = 'iban'
        values.acc_number = iban.BBAN
        bankcode = iban.bankcode + iban.countrycode
    else:
        # No, try to convert to IBAN
        country = pool.get('res.partner').browse(
            cursor, uid, partner_id).country_id
        values.state = 'bank'
        values.acc_number = account_number
        if country.code in sepa.IBAN.countries:
            account_info = sepa.online.account_info(country.code,
                                                    values.acc_number
                                                   )
            if account_info:
                values.iban = iban = account_info.iban
                values.state = 'iban'
                bankcode = account_info.code
                bic = account_info.bic

    if bic:
        values.bank_id = get_or_create_bank(pool, cursor, uid, bic)

    elif bankcode:
        # Try to link bank
        bank_obj = pool.get('res.bank')
        bank_ids = bank_obj.search(cursor, uid, [
            ('code', 'ilike', bankcode)
        ])
        if bank_ids:
            # Check BIC on existing banks
            values.bank_id = bank_ids[0]
            bank = bank_obj.browse(cursor, uid, values.bank_id)
            if not bank.bic:
                bank_obj.write(cursor, uid, values.bank_id, dict(bic=bic))
        else:
            # New bank - create
            values.bank_id = bank_obj.create(cursor, uid, dict(
                code = account_info.code,
                # Only the first eight positions of BIC are used for bank
                # transfers, so ditch the rest.
                bic = account_info.bic[:8],
                name = account_info.bank,
                country_id = country.id,
            ))

    # Create bank account and return
    return pool.get('res.partner.bank').create(cursor, uid, values)
Exemple #19
0
    def import_statements_file(self, cursor, uid, ids, context):
        '''
        Import bank statements / bank transactions file.
        This method represents the business logic, the parser modules
        represent the decoding logic.
        '''
        banking_import = self.browse(cursor, uid, ids, context)[0]
        statements_file = banking_import.file
        data = base64.decodestring(statements_file)

        company_obj = self.pool.get('res.company')
        user_obj = self.pool.get('res.user')
        partner_bank_obj = self.pool.get('res.partner.bank')
        journal_obj = self.pool.get('account.journal')
        move_line_obj = self.pool.get('account.move.line')
        payment_line_obj = self.pool.get('payment.line')
        statement_obj = self.pool.get('account.bank.statement')
        statement_line_obj = self.pool.get('account.bank.statement.line')
        statement_file_obj = self.pool.get('account.banking.imported.file')
        payment_order_obj = self.pool.get('payment.order')
        currency_obj = self.pool.get('res.currency')

        # get the parser to parse the file
        parser_code = banking_import.parser
        parser = models.create_parser(parser_code)
        if not parser:
            raise osv.except_osv(
                _('ERROR!'),
                _('Unable to import parser %(parser)s. Parser class not found.') %
                {'parser': parser_code}
            )

        # Get the company
        company = (banking_import.company or
                   user_obj.browse(cursor, uid, uid, context).company_id)

        # Parse the file
        statements = parser.parse(data)

        if any([x for x in statements if not x.is_valid()]):
            raise osv.except_osv(
                _('ERROR!'),
                _('The imported statements appear to be invalid! Check your file.')
            )

        # Create the file now, as the statements need to be linked to it
        import_id = statement_file_obj.create(cursor, uid, dict(
            company_id = company.id,
            file = statements_file,
            state = 'unfinished',
            format = parser.name,
        ))

        # Results
        results = struct(
            stat_loaded_cnt = 0,
            trans_loaded_cnt = 0,
            stat_skipped_cnt = 0,
            trans_skipped_cnt = 0,
            trans_matched_cnt = 0,
            bank_costs_invoice_cnt = 0,
            error_cnt = 0,
            log = [],
        )

        # Caching
        error_accounts = {}
        info = {}
        imported_statement_ids = []
        linked_payments = {}
        linked_invoices = {}

        if statements:
            # Get default defaults
            def_pay_account_id = company.partner_id.property_account_payable.id
            def_rec_account_id = company.partner_id.property_account_receivable.id

            # Get interesting journals once
            journal_ids = journal_obj.search(cursor, uid, [
                ('type', 'in', ('sale','purchase',
                                'purchase_refund','sale_refund')),
                ('company_id', '=', company.id),
            ])
            # Get all unreconciled moves predating the last statement in one big
            # swoop. Assumption: the statements in the file are sorted in ascending
            # order of date.
            move_line_ids = move_line_obj.search(cursor, uid, [
                ('reconcile_id', '=', False),
                ('journal_id', 'in', journal_ids),
                ('account_id.reconcile', '=', True),
                ('date', '<=', date2str(statements[-1].date)),
            ])
            if move_line_ids:
                move_lines = move_line_obj.browse(cursor, uid, move_line_ids)
            else:
                move_lines = []

            # Get all unreconciled sent payment lines in one big swoop.
            # No filtering can be done, as empty dates carry value for C2B
            # communication. Most likely there are much less sent payments
            # than reconciled and open/draft payments.
            cursor.execute("SELECT l.id FROM payment_order o, payment_line l "
                           "WHERE l.order_id = o.id AND "
                                 "o.state = 'sent' AND "
                                 "l.date_done IS NULL"
                          )
            payment_line_ids = [x[0] for x in cursor.fetchall()]
            if payment_line_ids:
                payment_lines = payment_line_obj.browse(cursor, uid, payment_line_ids)
            else:
                payment_lines = []

        for statement in statements:
            if statement.local_account in error_accounts:
                # Don't repeat messages
                results.stat_skipped_cnt += 1
                results.trans_skipped_cnt += len(statement.transactions)
                continue

            # Create fallback currency code
            currency_code = statement.local_currency or company.currency_id.name

            # Check cache for account info/currency
            if statement.local_account in info and \
               currency_code in info[statement.local_account]:
                account_info = info[statement.local_account][currency_code]

            else:
                # Pull account info/currency
                account_info = get_company_bank_account(
                    self.pool, cursor, uid, statement.local_account,
                    statement.local_currency, company, results.log
                )
                if not account_info:
                    results.log.append(
                        _('Statements found for unknown account %(bank_account)s') %
                        {'bank_account': statement.local_account}
                    )
                    error_accounts[statement.local_account] = True
                    results.error_cnt += 1
                    continue
                if 'journal_id' not in account_info:
                    results.log.append(
                        _('Statements found for account %(bank_account)s, '
                          'but no default journal was defined.'
                         ) % {'bank_account': statement.local_account}
                    )
                    error_accounts[statement.local_account] = True
                    results.error_cnt += 1
                    continue

                # Get required currency code
                currency_code = account_info.currency_id.name

                # Cache results
                if not statement.local_account in info:
                    info[statement.local_account] = {
                        currency_code: account_info
                    }
                else:
                    info[statement.local_account][currency_code] = account_info

            # Final check: no coercion of currencies!
            if statement.local_currency \
               and account_info.currency_id.name != statement.local_currency:
                # TODO: convert currencies?
                results.log.append(
                    _('Statement %(statement_id)s for account %(bank_account)s' 
                      ' uses different currency than the defined bank journal.'
                     ) % {
                         'bank_account': statement.local_account,
                         'statement_id': statement.id
                     }
                )
                error_accounts[statement.local_account] = True
                results.error_cnt += 1
                continue

            # Check existence of previous statement
            statement_ids = statement_obj.search(cursor, uid, [
                ('name', '=', statement.id),
                ('date', '=', date2str(statement.date)),
            ])
            if statement_ids:
                results.log.append(
                    _('Statement %(id)s known - skipped') % {
                        'id': statement.id
                    }
                )
                continue

            statement_id = statement_obj.create(cursor, uid, dict(
                name = statement.id,
                journal_id = account_info.journal_id.id,
                date = date2str(statement.date),
                balance_start = statement.start_balance,
                balance_end_real = statement.end_balance,
                balance_end = statement.end_balance,
                state = 'draft',
                user_id = uid,
                banking_id = import_id,
            ))
            imported_statement_ids.append(statement_id)

            # move each transaction to the right period and try to match it with an
            # invoice or payment
            subno = 0
            injected = []
            i = 0
            max_trans = len(statement.transactions)
            while i < max_trans:
                move_info = False
                if injected:
                    # Force FIFO behavior
                    transaction = injected.pop(0)
                else:
                    transaction = statement.transactions[i]
                    # Keep a tracer for identification of order in a statement in case
                    # of missing transaction ids.
                    subno += 1

                # Link accounting period
                period_id = get_period(self.pool, cursor, 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
                    costs = transaction.copy()
                    costs.type = bt.BANK_COSTS
                    costs.id = '%s-prov' % transaction.id
                    costs.transferred_amount = transaction.provision_costs
                    costs.remote_currency = transaction.provision_costs_currency
                    costs.message = transaction.provision_costs_description
                    injected.append(costs)

                    # Remove bank costs from current transaction
                    # Note that this requires that the transferred_amount
                    # includes the bank costs and that the costs itself are
                    # signed correctly.
                    transaction.transferred_amount -= transaction.provision_costs
                    transaction.provision_costs = None
                    transaction.provision_costs_currency = None
                    transaction.provision_costs_description = None

                # Allow inclusion of generated bank invoices
                if transaction.type == bt.BANK_COSTS:
                    lines = self._link_costs(
                        cursor, uid, transaction, period_id, account_info,
                        results.log
                    )
                    results.bank_costs_invoice_cnt += bool(lines)
                    for line in lines:
                        if not [x for x in move_lines if x.id == line.id]:
                            move_lines.append(line)
                    partner_ids = [account_info.bank_partner_id.id]
                    partner_banks = []

                else:
                    # Link remote partner, import account when needed
                    partner_banks = get_bank_accounts(
                        self.pool, cursor, uid, transaction.remote_account,
                        results.log, fail=True
                    )
                    if partner_banks:
                        partner_ids = [x.partner_id.id for x in partner_banks]
                    elif transaction.remote_owner:
                        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
                        elif hasattr(parser, 'country_code') and parser.country_code:
                            country_code = parser.country_code
                        else:
                            country_code = None
                        partner_id = get_or_create_partner(
                            self.pool, cursor, uid, transaction.remote_owner,
                            transaction.remote_owner_address,
                            transaction.remote_owner_postalcode,
                            transaction.remote_owner_city,
                            country_code, results.log
                        )
                        if transaction.remote_account:
                            partner_bank_id = create_bank_account(
                                self.pool, cursor, uid, partner_id,
                                transaction.remote_account,
                                transaction.remote_owner, 
                                transaction.remote_owner_address,
                                transaction.remote_owner_city,
                                country_code, results.log
                            )
                            partner_banks = partner_bank_obj.browse(
                                cursor, uid, [partner_bank_id]
                            )
                        else:
                            partner_bank_id = None
                            partner_banks = []
                        partner_ids = [partner_id]
                    else:
                        partner_ids = []
                        partner_banks = []

                # Credit means payment... isn't it?
                if transaction.transferred_amount < 0 and payment_lines:
                    # Link open payment - if any
                    move_info = self._link_payment(
                        cursor, uid, transaction,
                        payment_lines, partner_ids,
                        partner_banks, results.log, linked_payments,
                        )

                # Second guess, invoice -> may split transaction, so beware
                if not move_info:
                    # Link invoice - if any. Although bank costs are not an
                    # invoice, automatic invoicing on bank costs will create
                    # these, and invoice matching still has to be done.
                    move_info, remainder = self._link_invoice(
                        cursor, uid, transaction, move_lines, partner_ids,
                        partner_banks, results.log, linked_invoices,
                        )
                    if remainder:
                        injected.append(remainder)

                if not move_info:
                    # Use the default settings, but allow individual partner
                    # 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 len(partner_banks) == 1:
                            account_id = partner_banks[0].partner_id.property_account_payable
                        if len(partner_banks) != 1 or not account_id or account_id.id == def_pay_account_id:
                            account_id = account_info.default_credit_account_id
                    else:
                        if len(partner_banks) == 1:
                            account_id = partner_banks[0].partner_id.property_account_receivable
                        if len(partner_banks) != 1 or not account_id or account_id.id == def_rec_account_id:
                            account_id = account_info.default_debit_account_id
                else:
                    account_id = move_info.move_line.account_id
                    results.trans_matched_cnt += 1

                values = struct(
                    name = '%s.%s' % (statement.id, transaction.id or subno),
                    date = transaction.effective_date,
                    amount = transaction.transferred_amount,
                    account_id = account_id.id,
                    statement_id = statement_id,
                    note = transaction.message,
                    ref = transaction.reference,
                    period_id = period_id,
                    currency = account_info.currency_id.id,
                )
                if move_info:
                    values.type = move_info.type
                    values.reconcile_id = move_info.move_line.reconcile_id
                    values.partner_id = move_info.partner_id
                    values.partner_bank_id = move_info.partner_bank_id
                else:
                    values.partner_id = values.partner_bank_id = False
                if not values.partner_id and partner_ids and len(partner_ids) == 1:
                    values.partner_id = partner_ids[0]
                if not values.partner_bank_id and partner_banks and \
                    len(partner_banks) == 1:
                    values.partner_bank_id = partner_banks[0].id

                statement_line_id = statement_line_obj.create(cursor, uid, values)
                results.trans_loaded_cnt += 1
                # Only increase index when all generated transactions are
                # processed as well
                if not injected:
                    i += 1

            results.stat_loaded_cnt += 1
            
        if payment_lines:
            # As payments lines are treated as individual transactions, the
            # batch as a whole is only marked as 'done' when all payment lines
            # have been reconciled.
            cursor.execute(
                "SELECT DISTINCT o.id "
                "FROM payment_order o, payment_line l "
                "WHERE o.state = 'sent' "
                  "AND o.id = l.order_id "
                  "AND o.id NOT IN ("
                    "SELECT DISTINCT order_id AS id "
                    "FROM payment_line "
                    "WHERE date_done IS NULL "
                      "AND id IN (%s)"
                   ")" % (','.join([str(x) for x in payment_line_ids]))
            )
            order_ids = [x[0] for x in cursor.fetchall()]
            if order_ids:
                # Use workflow logics for the orders. Recode logic from
                # account_payment, in order to increase efficiency.
                payment_order_obj.set_done(cursor, uid, order_ids,
                                        {'state': 'done'}
                                       )
                wf_service = netsvc.LocalService('workflow')
                for id in order_ids:
                    wf_service.trg_validate(uid, 'payment.order', id, 'done',
                                            cursor
                                           )

            # Original code. Didn't take workflow logistics into account...
            #
            #cursor.execute(
            #    "UPDATE payment_order o "
            #    "SET state = 'done', "
            #        "date_done = '%s' "
            #    "FROM payment_line l "
            #    "WHERE o.state = 'sent' "
            #      "AND o.id = l.order_id "
            #      "AND l.id NOT IN ("
            #        "SELECT DISTINCT id FROM payment_line "
            #        "WHERE date_done IS NULL "
            #          "AND id IN (%s)"
            #       ")" % (
            #           time.strftime('%Y-%m-%d'),
            #           ','.join([str(x) for x in payment_line_ids])
            #       )
            #)

        report = [
            '%s: %s' % (_('Total number of statements'),
                        results.stat_skipped_cnt + results.stat_loaded_cnt),
            '%s: %s' % (_('Total number of transactions'),
                        results.trans_skipped_cnt + results.trans_loaded_cnt),
            '%s: %s' % (_('Number of errors found'),
                        results.error_cnt),
            '%s: %s' % (_('Number of statements skipped due to errors'),
                        results.stat_skipped_cnt),
            '%s: %s' % (_('Number of transactions skipped due to errors'),
                        results.trans_skipped_cnt),
            '%s: %s' % (_('Number of statements loaded'),
                        results.stat_loaded_cnt),
            '%s: %s' % (_('Number of transactions loaded'),
                        results.trans_loaded_cnt),
            '%s: %s' % (_('Number of transactions matched'),
                        results.trans_matched_cnt),
            '%s: %s' % (_('Number of bank costs invoices created'),
                        results.bank_costs_invoice_cnt),
            '',
            '%s:' % ('Error report'),
            '',
        ]
        text_log = '\n'.join(report + results.log)
        state = results.error_cnt and 'error' or 'ready'
        statement_file_obj.write(cursor, uid, import_id, dict(
            state = state, log = text_log,
            ), context)
        if not imported_statement_ids:
            # file state can be 'ready' while import state is 'error'
            state = 'error'
        self.write(cursor, uid, [ids[0]], dict(
                import_id = import_id, log = text_log, state = state,
                statement_ids = [[6, 0, imported_statement_ids]],
                ), context)
        return {
            'name': _('Import Bank Transactions File'),
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'res_model': self._name,
            'domain': [],
            'context': dict(context, active_ids=ids),
            'type': 'ir.actions.act_window',
            'target': 'new',
            'res_id': ids[0] or False,
        }
Exemple #20
0
def get_or_create_bank(pool, cursor, uid, bic, online=False, code=None,
                       name=None):
    '''
    Find or create the bank with the provided BIC code.
    When online, the SWIFT database will be consulted in order to
    provide for missing information.
    '''
    # UPDATE: Free SWIFT databases are since 2/22/2010 hidden behind an
    #         image challenge/response interface.

    bank_obj = pool.get('res.bank')

    # Self generated key?
    if len(bic) < 8:
        # search key
        bank_ids = bank_obj.search(
            cursor, uid, [
                ('code', '=', bic[:6])
            ])
        if not bank_ids:
            bank_ids = bank_obj.search(
                cursor, uid, [
                    ('bic', 'ilike', bic + '%')
                ])
    else:
        bank_ids = bank_obj.search(
            cursor, uid, [
                ('bic', '=', bic)
            ])

    if bank_ids and len(bank_ids) == 1:
        banks = bank_obj.browse(cursor, 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])]
    )
    country_id = country_ids and country_ids[0] or False
    bank_id = False

    if online:
        info, address = sepa.online.bank_info(bic)
        if info:
            bank_id = bank_obj.create(cursor, uid, dict(
                code = info.code,
                name = info.name,
                street = address.street,
                street2 = address.street2,
                zip = address.zip,
                city = address.city,
                country = country_id,
                bic = info.bic[:8],
            ))
    else:
        info = struct(name=name, code=code)

    if not online or not bank_id:
        bank_id = bank_obj.create(cursor, uid, dict(
            code = info.code or 'UNKNOW',
            name = info.name or _('Unknown Bank'),
            country = country_id,
            bic = bic,
        ))
    return bank_id, country_id
Exemple #21
0
def bank_info(bic):
    '''
    Consult the free online SWIFT service to obtain the name and address of a
    bank. This call may take several seconds to complete, due to the number of
    requests to make. In total three HTTP requests are made per function call.
    In theory one request could be stripped, but the SWIFT terms of use prevent
    automated usage, so user like behavior is required.
    '''
    def harvest(soup):
        retval = struct()
        for trsoup in soup('tr'):
            for stage, tdsoup in enumerate(trsoup('td')):
                if stage == 0:
                    attr = tdsoup.contents[0].strip().replace(' ','_')
                elif stage == 2:
                    if tdsoup.contents:
                        retval[attr] = tdsoup.contents[0].strip()
                    else:
                        retval[attr] = ''
        return retval

    # Get form
    agent = URLAgent()
    request = agent.open(SWIFTlink)
    soup = BeautifulSoup(request)

    # Parse request form. As this form is intertwined with a table, use the parent
    # as root to search for form elements.
    form = SoupForm(soup.find('form', {'id': 'frmFreeSearch1'}), parent=True)

    # Fill form fields
    form['selected_bic'] = bic

    # Get intermediate response
    response = agent.submit(form)

    # Parse response
    soup = BeautifulSoup(response)

    # Isolate the full 11 BIC - there may be more, but we only use the first
    bic_button = soup.find('a', {'class': 'bigbuttonblack'})
    if not bic_button:
        return None, None

    # Overwrite the location with 'any' ('XXX') to narrow the results to one or less.
    # Assume this regexp will never fail...
    full_bic = bic_re.match(bic_button.get('href')).groups()[0][:8] + 'XXX'

    # Get the detail form
    form = SoupForm(soup.find('form', {'id': 'frmDetail'}))

    # Fill detail fields
    form['selected_bic11'] = full_bic

    # Get final response
    response = agent.submit(form)
    soup = BeautifulSoup(response)

    # Now parse the results
    tables = soup.find('div', {'id':'Middle'}).findAll('table')
    if not tables:
        return None, None
    tablesoup = tables[2]('table')
    if not tablesoup:
        return None, None
    
    codes = harvest(tablesoup[0])
    if not codes:
        return None, None

    bankinfo = struct(
        # Most banks use the first four chars of the BIC as an identifier for
        # their 'virtual bank' accross the world, containing all national
        # banks world wide using the same name.
        # The concatenation with the two character country code is for most
        # national branches sufficient as a unique identifier.
        code = full_bic[:6],
        bic = full_bic,
        name = codes.Institution_name,
    )

    address = harvest(tablesoup[1])
    # The address in the SWIFT database includes a postal code.
    # We need to split it into two fields...
    if not address.Zip_Code:
        if address.Location:
            iso, address.Zip_Code, address.Location = \
                    postalcode.split(address.Location, full_bic[4:6])

    bankaddress = struct(
        street = address.Address.title(),
        city = address.Location.strip().title(),
        zip = address.Zip_Code,
        country = address.Country.title(),
        country_id = full_bic[4:6],
    )
    if '  ' in bankaddress.street:
        bankaddress.street, bankaddress.street2 = [
            x.strip() for x in bankaddress.street.split('  ', 1)
        ]
    else:
        bankaddress.street2 = ''

    return bankinfo, bankaddress
def create_bank_account(pool, cursor, uid, partner_id, account_number,
                        holder_name, log):
    '''
    Create a matching bank account with this holder for this partner.
    '''
    values = struct(
        partner_id=partner_id,
        owner_name=holder_name,
    )
    bankcode = None
    bic = None

    # Are we dealing with IBAN?
    iban = sepa.IBAN(account_number)
    if iban.valid:
        values.state = 'iban'
        values.acc_number = iban.BBAN
        bankcode = iban.bankcode + iban.countrycode
    else:
        # No, try to convert to IBAN
        country = pool.get('res.partner').browse(cursor, uid,
                                                 partner_id).country_id
        values.state = 'bank'
        values.acc_number = account_number
        if country.code in sepa.IBAN.countries:
            account_info = sepa.online.account_info(country.code,
                                                    values.acc_number)
            if account_info:
                values.iban = iban = account_info.iban
                values.state = 'iban'
                bankcode = account_info.code
                bic = account_info.bic

    if bic:
        values.bank_id = get_or_create_bank(pool, cursor, uid, bic)

    elif bankcode:
        # Try to link bank
        bank_obj = pool.get('res.bank')
        bank_ids = bank_obj.search(cursor, uid, [('code', 'ilike', bankcode)])
        if bank_ids:
            # Check BIC on existing banks
            values.bank_id = bank_ids[0]
            bank = bank_obj.browse(cursor, uid, values.bank_id)
            if not bank.bic:
                bank_obj.write(cursor, uid, values.bank_id, dict(bic=bic))
        else:
            # New bank - create
            values.bank_id = bank_obj.create(
                cursor,
                uid,
                dict(
                    code=account_info.code,
                    # Only the first eight positions of BIC are used for bank
                    # transfers, so ditch the rest.
                    bic=account_info.bic[:8],
                    name=account_info.bank,
                    country_id=country.id,
                ))

    # Create bank account and return
    return pool.get('res.partner.bank').create(cursor, uid, values)
    def import_statements_file(self, cursor, uid, ids, context):
        '''
        Import bank statements / bank transactions file.
        This method represents the business logic, the parser modules
        represent the decoding logic.
        '''
        banking_import = self.browse(cursor, uid, ids, context)[0]
        statements_file = banking_import.file
        data = base64.decodestring(statements_file)

        company_obj = self.pool.get('res.company')
        user_obj = self.pool.get('res.user')
        partner_bank_obj = self.pool.get('res.partner.bank')
        journal_obj = self.pool.get('account.journal')
        move_line_obj = self.pool.get('account.move.line')
        payment_line_obj = self.pool.get('payment.line')
        statement_obj = self.pool.get('account.bank.statement')
        statement_line_obj = self.pool.get('account.bank.statement.line')
        statement_file_obj = self.pool.get('account.banking.imported.file')
        payment_order_obj = self.pool.get('payment.order')
        currency_obj = self.pool.get('res.currency')

        # get the parser to parse the file
        parser_code = banking_import.parser
        parser = models.create_parser(parser_code)
        if not parser:
            raise osv.except_osv(
                _('ERROR!'),
                _('Unable to import parser %(parser)s. Parser class not found.'
                  ) % {'parser': parser_code})

        # Get the company
        company = (banking_import.company
                   or user_obj.browse(cursor, uid, uid, context).company_id)

        # Parse the file
        statements = parser.parse(data)

        if any([x for x in statements if not x.is_valid()]):
            raise osv.except_osv(
                _('ERROR!'),
                _('The imported statements appear to be invalid! Check your file.'
                  ))

        # Create the file now, as the statements need to be linked to it
        import_id = statement_file_obj.create(
            cursor, uid,
            dict(
                company_id=company.id,
                file=statements_file,
                state='unfinished',
                format=parser.name,
            ))

        # Results
        results = struct(
            stat_loaded_cnt=0,
            trans_loaded_cnt=0,
            stat_skipped_cnt=0,
            trans_skipped_cnt=0,
            trans_matched_cnt=0,
            bank_costs_invoice_cnt=0,
            error_cnt=0,
            log=[],
        )

        # Caching
        error_accounts = {}
        info = {}
        imported_statement_ids = []
        linked_payments = {}
        linked_invoices = {}

        if statements:
            # Get default defaults
            def_pay_account_id = company.partner_id.property_account_payable.id
            def_rec_account_id = company.partner_id.property_account_receivable.id

            # Get interesting journals once
            journal_ids = journal_obj.search(cursor, uid, [
                ('type', 'in',
                 ('sale', 'purchase', 'purchase_refund', 'sale_refund')),
                ('company_id', '=', company.id),
            ])
            # Get all unreconciled moves predating the last statement in one big
            # swoop. Assumption: the statements in the file are sorted in ascending
            # order of date.
            move_line_ids = move_line_obj.search(cursor, uid, [
                ('reconcile_id', '=', False),
                ('journal_id', 'in', journal_ids),
                ('account_id.reconcile', '=', True),
                ('date', '<=', date2str(statements[-1].date)),
            ])
            if move_line_ids:
                move_lines = move_line_obj.browse(cursor, uid, move_line_ids)
            else:
                move_lines = []

            # Get all unreconciled sent payment lines in one big swoop.
            # No filtering can be done, as empty dates carry value for C2B
            # communication. Most likely there are much less sent payments
            # than reconciled and open/draft payments.
            cursor.execute("SELECT l.id FROM payment_order o, payment_line l "
                           "WHERE l.order_id = o.id AND "
                           "o.state = 'sent' AND "
                           "l.date_done IS NULL")
            payment_line_ids = [x[0] for x in cursor.fetchall()]
            if payment_line_ids:
                payment_lines = payment_line_obj.browse(
                    cursor, uid, payment_line_ids)
            else:
                payment_lines = []

        for statement in statements:
            if statement.local_account in error_accounts:
                # Don't repeat messages
                results.stat_skipped_cnt += 1
                results.trans_skipped_cnt += len(statement.transactions)
                continue

            # Create fallback currency code
            currency_code = statement.local_currency or company.currency_id.name

            # Check cache for account info/currency
            if statement.local_account in info and \
               currency_code in info[statement.local_account]:
                account_info = info[statement.local_account][currency_code]

            else:
                # Pull account info/currency
                account_info = get_company_bank_account(
                    self.pool, cursor, uid, statement.local_account,
                    statement.local_currency, company, results.log)
                if not account_info:
                    results.log.append(
                        _('Statements found for unknown account %(bank_account)s'
                          ) % {'bank_account': statement.local_account})
                    error_accounts[statement.local_account] = True
                    results.error_cnt += 1
                    continue
                if 'journal_id' not in account_info:
                    results.log.append(
                        _('Statements found for account %(bank_account)s, '
                          'but no default journal was defined.') %
                        {'bank_account': statement.local_account})
                    error_accounts[statement.local_account] = True
                    results.error_cnt += 1
                    continue

                # Get required currency code
                currency_code = account_info.currency_id.name

                # Cache results
                if not statement.local_account in info:
                    info[statement.local_account] = {
                        currency_code: account_info
                    }
                else:
                    info[statement.local_account][currency_code] = account_info

            # Final check: no coercion of currencies!
            if statement.local_currency \
               and account_info.currency_id.name != statement.local_currency:
                # TODO: convert currencies?
                results.log.append(
                    _('Statement %(statement_id)s for account %(bank_account)s'
                      ' uses different currency than the defined bank journal.'
                      ) % {
                          'bank_account': statement.local_account,
                          'statement_id': statement.id
                      })
                error_accounts[statement.local_account] = True
                results.error_cnt += 1
                continue

            # Check existence of previous statement
            statement_ids = statement_obj.search(cursor, uid, [
                ('name', '=', statement.id),
                ('date', '=', date2str(statement.date)),
            ])
            if statement_ids:
                results.log.append(
                    _('Statement %(id)s known - skipped') %
                    {'id': statement.id})
                continue

            statement_id = statement_obj.create(
                cursor, uid,
                dict(
                    name=statement.id,
                    journal_id=account_info.journal_id.id,
                    date=date2str(statement.date),
                    balance_start=statement.start_balance,
                    balance_end_real=statement.end_balance,
                    balance_end=statement.end_balance,
                    state='draft',
                    user_id=uid,
                    banking_id=import_id,
                ))
            imported_statement_ids.append(statement_id)

            # move each transaction to the right period and try to match it with an
            # invoice or payment
            subno = 0
            injected = []
            i = 0
            max_trans = len(statement.transactions)
            while i < max_trans:
                move_info = False
                if injected:
                    # Force FIFO behavior
                    transaction = injected.pop(0)
                else:
                    transaction = statement.transactions[i]
                    # Keep a tracer for identification of order in a statement in case
                    # of missing transaction ids.
                    subno += 1

                # Link accounting period
                period_id = get_period(self.pool, cursor, 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
                    costs = transaction.copy()
                    costs.type = bt.BANK_COSTS
                    costs.id = '%s-prov' % transaction.id
                    costs.transferred_amount = transaction.provision_costs
                    costs.remote_currency = transaction.provision_costs_currency
                    costs.message = transaction.provision_costs_description
                    injected.append(costs)

                    # Remove bank costs from current transaction
                    # Note that this requires that the transferred_amount
                    # includes the bank costs and that the costs itself are
                    # signed correctly.
                    transaction.transferred_amount -= transaction.provision_costs
                    transaction.provision_costs = None
                    transaction.provision_costs_currency = None
                    transaction.provision_costs_description = None

                # Allow inclusion of generated bank invoices
                if transaction.type == bt.BANK_COSTS:
                    lines = self._link_costs(cursor, uid, transaction,
                                             period_id, account_info,
                                             results.log)
                    results.bank_costs_invoice_cnt += bool(lines)
                    for line in lines:
                        if not [x for x in move_lines if x.id == line.id]:
                            move_lines.append(line)
                    partner_ids = [account_info.bank_partner_id.id]
                    partner_banks = []

                else:
                    # Link remote partner, import account when needed
                    partner_banks = get_bank_accounts(
                        self.pool,
                        cursor,
                        uid,
                        transaction.remote_account,
                        results.log,
                        fail=True)
                    if partner_banks:
                        partner_ids = [x.partner_id.id for x in partner_banks]
                    elif transaction.remote_owner:
                        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
                        elif hasattr(parser,
                                     'country_code') and parser.country_code:
                            country_code = parser.country_code
                        else:
                            country_code = None
                        partner_id = get_or_create_partner(
                            self.pool, cursor, uid, transaction.remote_owner,
                            transaction.remote_owner_address,
                            transaction.remote_owner_postalcode,
                            transaction.remote_owner_city, country_code,
                            results.log)
                        if transaction.remote_account:
                            partner_bank_id = create_bank_account(
                                self.pool, cursor, uid, partner_id,
                                transaction.remote_account,
                                transaction.remote_owner,
                                transaction.remote_owner_address,
                                transaction.remote_owner_city, country_code,
                                results.log)
                            partner_banks = partner_bank_obj.browse(
                                cursor, uid, [partner_bank_id])
                        else:
                            partner_bank_id = None
                            partner_banks = []
                        partner_ids = [partner_id]
                    else:
                        partner_ids = []
                        partner_banks = []

                # Credit means payment... isn't it?
                if transaction.transferred_amount < 0 and payment_lines:
                    # Link open payment - if any
                    move_info = self._link_payment(
                        cursor,
                        uid,
                        transaction,
                        payment_lines,
                        partner_ids,
                        partner_banks,
                        results.log,
                        linked_payments,
                    )

                # Second guess, invoice -> may split transaction, so beware
                if not move_info:
                    # Link invoice - if any. Although bank costs are not an
                    # invoice, automatic invoicing on bank costs will create
                    # these, and invoice matching still has to be done.
                    move_info, remainder = self._link_invoice(
                        cursor,
                        uid,
                        transaction,
                        move_lines,
                        partner_ids,
                        partner_banks,
                        results.log,
                        linked_invoices,
                    )
                    if remainder:
                        injected.append(remainder)

                if not move_info:
                    # Use the default settings, but allow individual partner
                    # 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 len(partner_banks) == 1:
                            account_id = partner_banks[
                                0].partner_id.property_account_payable
                        if len(
                                partner_banks
                        ) != 1 or not account_id or account_id.id == def_pay_account_id:
                            account_id = account_info.default_credit_account_id
                    else:
                        if len(partner_banks) == 1:
                            account_id = partner_banks[
                                0].partner_id.property_account_receivable
                        if len(
                                partner_banks
                        ) != 1 or not account_id or account_id.id == def_rec_account_id:
                            account_id = account_info.default_debit_account_id
                else:
                    account_id = move_info.move_line.account_id
                    results.trans_matched_cnt += 1

                values = struct(
                    name='%s.%s' % (statement.id, transaction.id or subno),
                    date=transaction.effective_date,
                    amount=transaction.transferred_amount,
                    account_id=account_id.id,
                    statement_id=statement_id,
                    note=transaction.message,
                    ref=transaction.reference,
                    period_id=period_id,
                    currency=account_info.currency_id.id,
                )
                if move_info:
                    values.type = move_info.type
                    values.reconcile_id = move_info.move_line.reconcile_id
                    values.partner_id = move_info.partner_id
                    values.partner_bank_id = move_info.partner_bank_id
                else:
                    values.partner_id = values.partner_bank_id = False
                if not values.partner_id and partner_ids and len(
                        partner_ids) == 1:
                    values.partner_id = partner_ids[0]
                if not values.partner_bank_id and partner_banks and \
                    len(partner_banks) == 1:
                    values.partner_bank_id = partner_banks[0].id

                statement_line_id = statement_line_obj.create(
                    cursor, uid, values)
                results.trans_loaded_cnt += 1
                # Only increase index when all generated transactions are
                # processed as well
                if not injected:
                    i += 1

            results.stat_loaded_cnt += 1

        if payment_lines:
            # As payments lines are treated as individual transactions, the
            # batch as a whole is only marked as 'done' when all payment lines
            # have been reconciled.
            cursor.execute("SELECT DISTINCT o.id "
                           "FROM payment_order o, payment_line l "
                           "WHERE o.state = 'sent' "
                           "AND o.id = l.order_id "
                           "AND o.id NOT IN ("
                           "SELECT DISTINCT order_id AS id "
                           "FROM payment_line "
                           "WHERE date_done IS NULL "
                           "AND id IN (%s)"
                           ")" % (','.join([str(x)
                                            for x in payment_line_ids])))
            order_ids = [x[0] for x in cursor.fetchall()]
            if order_ids:
                # Use workflow logics for the orders. Recode logic from
                # account_payment, in order to increase efficiency.
                payment_order_obj.set_done(cursor, uid, order_ids,
                                           {'state': 'done'})
                wf_service = netsvc.LocalService('workflow')
                for id in order_ids:
                    wf_service.trg_validate(uid, 'payment.order', id, 'done',
                                            cursor)

            # Original code. Didn't take workflow logistics into account...
            #
            #cursor.execute(
            #    "UPDATE payment_order o "
            #    "SET state = 'done', "
            #        "date_done = '%s' "
            #    "FROM payment_line l "
            #    "WHERE o.state = 'sent' "
            #      "AND o.id = l.order_id "
            #      "AND l.id NOT IN ("
            #        "SELECT DISTINCT id FROM payment_line "
            #        "WHERE date_done IS NULL "
            #          "AND id IN (%s)"
            #       ")" % (
            #           time.strftime('%Y-%m-%d'),
            #           ','.join([str(x) for x in payment_line_ids])
            #       )
            #)

        report = [
            '%s: %s' % (_('Total number of statements'),
                        results.stat_skipped_cnt + results.stat_loaded_cnt),
            '%s: %s' % (_('Total number of transactions'),
                        results.trans_skipped_cnt + results.trans_loaded_cnt),
            '%s: %s' % (_('Number of errors found'), results.error_cnt),
            '%s: %s' % (_('Number of statements skipped due to errors'),
                        results.stat_skipped_cnt),
            '%s: %s' % (_('Number of transactions skipped due to errors'),
                        results.trans_skipped_cnt),
            '%s: %s' %
            (_('Number of statements loaded'), results.stat_loaded_cnt),
            '%s: %s' %
            (_('Number of transactions loaded'), results.trans_loaded_cnt),
            '%s: %s' %
            (_('Number of transactions matched'), results.trans_matched_cnt),
            '%s: %s' % (_('Number of bank costs invoices created'),
                        results.bank_costs_invoice_cnt),
            '',
            '%s:' % ('Error report'),
            '',
        ]
        text_log = '\n'.join(report + results.log)
        state = results.error_cnt and 'error' or 'ready'
        statement_file_obj.write(cursor, uid, import_id,
                                 dict(
                                     state=state,
                                     log=text_log,
                                 ), context)
        if not imported_statement_ids:
            # file state can be 'ready' while import state is 'error'
            state = 'error'
        self.write(
            cursor, uid, [ids[0]],
            dict(
                import_id=import_id,
                log=text_log,
                state=state,
                statement_ids=[[6, 0, imported_statement_ids]],
            ), context)
        return {
            'name': _('Import Bank Transactions File'),
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'res_model': self._name,
            'domain': [],
            'context': dict(context, active_ids=ids),
            'type': 'ir.actions.act_window',
            'target': 'new',
            'res_id': ids[0] or False,
        }