Пример #1
0
    def _complete_stmts_vals(self, stmts_vals, journal, account_number):
        for st_vals in stmts_vals:
            st_vals['journal_id'] = journal.id
            if not st_vals.get('reference'):
                st_vals['reference'] = self.filename
            if st_vals.get('number'):
                #build the full name like BNK/2016/00135 by just giving the number '135'
                st_vals['name'] = journal.sequence_id.with_context(
                    ir_sequence_date=st_vals.get('date')).get_next_char(
                        st_vals['number'])
                del (st_vals['number'])
            for line_vals in st_vals['transactions']:
                unique_import_id = line_vals.get('unique_import_id')
                if unique_import_id:
                    sanitized_account_number = sanitize_account_number(
                        account_number)
                    line_vals['unique_import_id'] = (
                        sanitized_account_number and sanitized_account_number +
                        '-' or '') + str(journal.id) + '-' + unique_import_id

                if not line_vals.get('bank_account_id'):
                    # Find the partner and his bank account or create the bank account. The partner selected during the
                    # reconciliation process will be linked to the bank when the statement is closed.
                    identifying_string = line_vals.get('account_number')
                    if identifying_string:
                        partner_bank = self.env['res.partner.bank']\
                            .search([('acc_number', '=', identifying_string), ('company_id', 'in', (False, journal.company_id.id))], limit=1)
                        if partner_bank:
                            line_vals['bank_account_id'] = partner_bank.id
                            line_vals[
                                'partner_id'] = partner_bank.partner_id.id
        return stmts_vals
Пример #2
0
    def _create_or_find_mandate(self, iban, partner_id):
        self.ensure_one()
        ResPartnerBank = self.env['res.partner.bank'].sudo()
        partner_bank = ResPartnerBank.search([
            ('sanitized_acc_number', '=', sanitize_account_number(iban))], limit=1)
        if not partner_bank:
            partner_bank = ResPartnerBank.create({
                'acc_number': iban,
                'partner_id': partner_id,
            })

        # avoid duplicate
        mandate = self.env['sdd.mandate'].sudo().search([
            ('state', 'not in', ['closed', 'revoked']),
            ('start_date', '<=', datetime.now()),
            '|', ('end_date', '>=', datetime.now()), ('end_date', '=', None),
            ('partner_id', '=', partner_id),
            ('partner_bank_id', '=', partner_bank.id),
            '|', ('one_off', '=', False), ('payment_ids', '=', False)], limit=1)
        if not mandate:
            mandate = self.env['sdd.mandate'].sudo().create({
                'partner_id': partner_id,
                'partner_bank_id': partner_bank.id,
                'start_date': datetime.now(),
                'payment_journal_id': self.journal_id.id,
                'state': 'draft',
            })
        return mandate
    def _complete_stmts_vals(self, stmts_vals, journal, account_number):
        for st_vals in stmts_vals:
            st_vals['journal_id'] = journal.id
            if not st_vals.get('reference'):
                st_vals['reference'] = " ".join(
                    self.attachment_ids.mapped('name'))
            for line_vals in st_vals['transactions']:
                unique_import_id = line_vals.get('unique_import_id')
                if unique_import_id:
                    sanitized_account_number = sanitize_account_number(
                        account_number)
                    line_vals['unique_import_id'] = (
                        sanitized_account_number and sanitized_account_number +
                        '-' or '') + str(journal.id) + '-' + unique_import_id

                if not line_vals.get('partner_bank_id'):
                    # Find the partner and his bank account or create the bank account. The partner selected during the
                    # reconciliation process will be linked to the bank when the statement is closed.
                    identifying_string = line_vals.get('account_number')
                    if identifying_string:
                        partner_bank = self.env['res.partner.bank'].search(
                            [('acc_number', '=', identifying_string)], limit=1)
                        if partner_bank:
                            line_vals['partner_bank_id'] = partner_bank.id
                            line_vals[
                                'partner_id'] = partner_bank.partner_id.id
        return stmts_vals
    def _complete_stmts_vals(self, stmts_vals, journal, account_number):
        for st_vals in stmts_vals:
            st_vals['journal_id'] = journal.id
            if not st_vals.get('reference'):
                st_vals['reference'] = self.filename
            if st_vals.get('number'):
                #build the full name like BNK/2016/00135 by just giving the number '135'
                st_vals['name'] = journal.sequence_id.with_context(ir_sequence_date=st_vals.get('date')).get_next_char(st_vals['number'])
                del(st_vals['number'])
            for line_vals in st_vals['transactions']:
                unique_import_id = line_vals.get('unique_import_id')
                if unique_import_id:
                    sanitized_account_number = sanitize_account_number(account_number)
                    line_vals['unique_import_id'] = (sanitized_account_number and sanitized_account_number + '-' or '') + str(journal.id) + '-' + unique_import_id

                if not line_vals.get('bank_account_id'):
                    # Find the partner and his bank account or create the bank account. The partner selected during the
                    # reconciliation process will be linked to the bank when the statement is closed.
                    identifying_string = line_vals.get('account_number')
                    if identifying_string:
                        partner_bank = self.env['res.partner.bank'].search([('acc_number', '=', identifying_string)], limit=1)
                        if partner_bank:
                            line_vals['bank_account_id'] = partner_bank.id
                            line_vals['partner_id'] = partner_bank.partner_id.id
        return stmts_vals
Пример #5
0
 def _compute_sanitized_acc_number(self):
     for bank_account in self:
         if bank_account.bank_id:
             acc_number_format = bank_account.bank_id.acc_number_format \
                 or '%(acc_number)s'
             args = {
                 'bra_number': bank_account.bra_number or '',
                 'bra_number_dig': bank_account.bra_number_dig or '',
                 'acc_number': bank_account.acc_number or '',
                 'acc_number_dig': bank_account.acc_number_dig or ''
             }
             self.sanitized_acc_number = sanitize_account_number(
                 acc_number_format % args)
         else:
             self.sanitized_acc_number = sanitize_account_number(
                 bank_account.acc_number)
Пример #6
0
    def _complete_stmts_vals(self, stmts_vals, journal, account_number):
        for st_vals in stmts_vals:
            st_vals["journal_id"] = journal.id
            for lvals in st_vals["transactions"]:
                unique_import_id = lvals.get("unique_import_id")
                if unique_import_id:
                    sanitized_account_number = sanitize_account_number(account_number)
                    lvals["unique_import_id"] = (
                        (
                            sanitized_account_number
                            and sanitized_account_number + "-"
                            or ""
                        )
                        + str(journal.id)
                        + "-"
                        + unique_import_id
                    )

                if (
                    not lvals.get("partner_bank_id")
                    and lvals.get("account_number")
                    and not lvals.get("partner_id")
                ):
                    # Find the partner from his bank account number
                    # The partner selected during the
                    # reconciliation process will be linked to the bank account
                    # when the statement is closed (code in the account module)
                    self._update_partner_from_account_number(lvals)
                if not lvals.get("payment_ref"):
                    raise UserError(_("Missing payment_ref on a transaction."))
        return stmts_vals
Пример #7
0
    def _find_additional_data(self, currency_code, account_number):
        """ Look for a res.currency and account.journal using values extracted from the
            statement and make sure it's consistent.
        """
        company_currency = self.env.user.company_id.currency_id
        journal_obj = self.env['account.journal']
        currency = None
        sanitized_account_number = sanitize_account_number(account_number)

        if currency_code:
            currency = self.env['res.currency'].search(
                [('name', '=ilike', currency_code)], limit=1)
            if not currency:
                raise UserError(
                    _("No currency found matching '%s'.") % currency_code)
            if currency == company_currency:
                currency = False

        journal = journal_obj.browse(self.env.context.get('journal_id', []))
        if account_number:
            # No bank account on the journal : create one from the account number of the statement
            if journal and not journal.bank_account_id:
                journal.set_bank_account(account_number)
            # No journal passed to the wizard : try to find one using the account number of the statement
            elif not journal:
                journal = journal_obj.search([
                    ('bank_account_id.sanitized_acc_number', '=',
                     sanitized_account_number)
                ])
            # Already a bank account on the journal : check it's the same as on the statement
            else:
                if not self._check_journal_bank_account(
                        journal, sanitized_account_number):
                    raise UserError(
                        _('The account of this statement (%s) is not the same as the journal (%s).'
                          ) %
                        (account_number, journal.bank_account_id.acc_number))

        # If importing into an existing journal, its currency must be the same as the bank statement
        if journal:
            journal_currency = journal.currency_id
            if currency is None:
                currency = journal_currency
            if currency and currency != journal_currency:
                statement_cur_code = not currency and company_currency.name or currency.name
                journal_cur_code = not journal_currency and company_currency.name or journal_currency.name
                raise UserError(
                    _('The currency of the bank statement (%s) is not the same as the currency of the journal (%s).'
                      ) % (statement_cur_code, journal_cur_code))

        # If we couldn't find / can't create a journal, everything is lost
        if not journal and not account_number:
            raise UserError(
                _('Cannot find in which journal import this statement. Please manually select a journal.'
                  ))

        return currency, journal
Пример #8
0
 def _validate_qr_iban(self, qr_iban):
     # Check first if it's a valid IBAN.
     validate_iban(qr_iban)
     # We sanitize first so that _check_qr_iban_range() can extract correct IID from IBAN to validate it.
     sanitized_qr_iban = sanitize_account_number(qr_iban)
     # Now, check if it's valid QR-IBAN (based on its IID).
     if not self._check_qr_iban_range(sanitized_qr_iban):
         raise ValidationError(_("QR-IBAN '%s' is invalid.") % qr_iban)
     return True
Пример #9
0
 def _prepare_swiss_code_url_vals(self, amount, currency_name,
                                  debtor_partner, reference_type, reference,
                                  comment):
     qr_code_vals = super()._prepare_swiss_code_url_vals(
         amount, currency_name, debtor_partner, reference_type, reference,
         comment)
     # If there is a QR IBAN we use it for the barcode instead of the account number
     if self.l10n_ch_qr_iban:
         qr_code_vals[3] = sanitize_account_number(self.l10n_ch_qr_iban)
     return qr_code_vals
Пример #10
0
 def _l10n_ch_get_qr_vals(self, amount, currency, debtor_partner,
                          free_communication, structured_communication):
     qr_vals = super()._l10n_ch_get_qr_vals(amount, currency,
                                            debtor_partner,
                                            free_communication,
                                            structured_communication)
     # If there is a QR IBAN we use it for the barcode instead of the account number
     if self.l10n_ch_qr_iban:
         qr_vals[3] = sanitize_account_number(self.l10n_ch_qr_iban)
     return qr_vals
Пример #11
0
 def _qonto_get_slug(self):
     self.ensure_one()
     url = QONTO_ENDPOINT + '/organizations/%7Bid%7D'
     response = requests.get(url, verify=False, headers=self._qonto_header())
     if response.status_code == 200:
         data = json.loads(response.text)
         res = {}
         for account in data.get('organization', {}).get('bank_accounts', []):
             iban = sanitize_account_number(account.get('iban', ''))
             res[iban] = account.get('slug')
         return res
     raise UserError(_('%s \n\n %s') % (response.status_code, response.text))
Пример #12
0
    def _onchange_set_l10n_ch_postal(self):
        try:
            validate_iban(self.bank_acc_number)
            is_iban = True
        except ValidationError:
            is_iban = False

        if is_iban:
            self.l10n_ch_postal = self.env[
                'res.partner.bank']._retrieve_l10n_ch_postal(
                    sanitize_account_number(self.bank_acc_number))
        else:
            self.l10n_ch_postal = self.bank_acc_number
 def _ponto_get_account_ids(self):
     url = PONTO_ENDPOINT + "/accounts"
     response = requests.get(
         url, params={"limit": 100}, headers=self._ponto_header()
     )
     if response.status_code == 200:
         data = json.loads(response.text)
         res = {}
         for account in data.get("data", []):
             iban = sanitize_account_number(
                 account.get("attributes", {}).get("reference", "")
             )
             res[iban] = account.get("id")
         return res
     raise UserError(_("%s \n\n %s") % (response.status_code, response.text))
Пример #14
0
def validate_qr_iban(qr_iban):
    # Check first if it's a valid IBAN.
    validate_iban(qr_iban)

    # We sanitize first so that _check_qr_iban_range() can extract correct IID from IBAN to validate it.
    sanitized_qr_iban = sanitize_account_number(qr_iban)

    if sanitized_qr_iban[:2] != 'CH':
        raise ValidationError(
            _("QR-IBAN numbers are only available in Switzerland."))

    # Now, check if it's valid QR-IBAN (based on its IID).
    if not check_qr_iban_range(sanitized_qr_iban):
        raise ValidationError(_("QR-IBAN '%s' is invalid.") % qr_iban)

    return True
 def _ponto_get_account_ids(self):
     url = PONTO_ENDPOINT + '/accounts'
     response = requests.get(url,
                             verify=False,
                             params={'limit': 100},
                             headers=self._ponto_header())
     if response.status_code == 200:
         data = json.loads(response.text)
         res = {}
         for account in data.get('data', []):
             iban = sanitize_account_number(
                 account.get('attributes', {}).get('reference', ''))
             res[iban] = account.get('id')
         return res
     raise UserError(
         _('%s \n\n %s') % (response.status_code, response.text))
Пример #16
0
    def _find_additional_data(self, currency_code, account_number):
        """ Look for a res.currency and account.journal using values extracted from the
            statement and make sure it's consistent.
        """
        company_currency = self.env.user.company_id.currency_id
        journal_obj = self.env['account.journal']
        currency = None
        sanitized_account_number = sanitize_account_number(account_number)

        if currency_code:
            currency = self.env['res.currency'].search([('name', '=ilike', currency_code)], limit=1)
            if not currency:
                raise UserError(_("No currency found matching '%s'.") % currency_code)
            if currency == company_currency:
                currency = False

        journal = journal_obj.browse(self.env.context.get('journal_id', []))
        if account_number:
            # No bank account on the journal : create one from the account number of the statement
            if journal and not journal.bank_account_id:
                journal.set_bank_account(account_number)
            # No journal passed to the wizard : try to find one using the account number of the statement
            elif not journal:
                journal = journal_obj.search([('bank_account_id.sanitized_acc_number', '=', sanitized_account_number)])
            # Already a bank account on the journal : check it's the same as on the statement
            else:
                if not self._check_journal_bank_account(journal, sanitized_account_number):
                    raise UserError(_('The account of this statement (%s) is not the same as the journal (%s).') % (account_number, journal.bank_account_id.acc_number))

        # If importing into an existing journal, its currency must be the same as the bank statement
        if journal:
            journal_currency = journal.currency_id
            if currency is None:
                currency = journal_currency
            if currency and currency != journal_currency:
                statement_cur_code = not currency and company_currency.name or currency.name
                journal_cur_code = not journal_currency and company_currency.name or journal_currency.name
                raise UserError(_('The currency of the bank statement (%s) is not the same as the currency of the journal (%s) !') % (statement_cur_code, journal_cur_code))

        # If we couldn't find / can't create a journal, everything is lost
        if not journal and not account_number:
            raise UserError(_('Cannot find in which journal import this statement. Please manually select a journal.'))

        return currency, journal
Пример #17
0
    def sepa_direct_debit_s2s_form_process(self, data):
        if not data.get('mandate_id'):
            iban = sanitize_account_number(data['iban'])

            # will raise a ValidationError given an invalid format
            validate_iban(iban)

            partner_id = int(data.get('partner_id'))
            mandate = self._create_or_find_mandate(iban, partner_id)
        else:
            mandate = self.env['sdd.mandate'].browse(data['mandate_id'])
            # since we're in a sudoed env, we need to add a few checks
            if mandate.partner_id.id != data['partner_id']:
                raise AccessError(_('Identity mismatch'))
        payment_token = self.env['payment.token'].sudo().create({
            'sdd_mandate_id': mandate.id,
            'name': data['iban'],
            'acquirer_ref': mandate.name,
            'acquirer_id': int(data['acquirer_id']),
            'partner_id': int(data['partner_id']),
        })
        return payment_token
Пример #18
0
    def sepa_direct_debit_s2s_form_process(self, data):
        partner_id = int(data['partner_id'])
        if not data.get('mandate_id'):
            iban = sanitize_account_number(data['iban'])

            same_iban_acc = self.env['res.partner.bank'].search(
                [('sanitized_acc_number', '=', iban),
                 ('company_id', '=', self.env.company.id)],
                limit=1)
            if same_iban_acc:
                raise ValidationError(
                    _("This account is not available. Please log in to continue."
                      ))

            # will raise a ValidationError given an invalid format
            validate_iban(iban)

            mandate = self._create_or_find_mandate(iban, partner_id)
        else:
            partner = self.env['res.partner'].browse(partner_id).sudo()
            mandate = self.env['sdd.mandate'].browse(data['mandate_id'])
            # since we're in a sudoed env, we need to add a few checks
            if mandate.partner_id != partner.commercial_partner_id:
                raise AccessError(_('Identity mismatch'))
        iban_mask = 'X' * (len(data['iban']) - 4) + data['iban'][-4:]
        payment_token = self.env['payment.token'].sudo().create({
            'sdd_mandate_id':
            mandate.id,
            'name':
            _('Direct Debit: ') + iban_mask,
            'acquirer_ref':
            mandate.name,
            'acquirer_id':
            int(data['acquirer_id']),
            'partner_id':
            partner_id,
        })
        return payment_token
Пример #19
0
 def set_bank_account(self, acc_number, bank_id=None):
     """ Create a res.partner.bank (if not exists) and set it as value of the field bank_account_id """
     self.ensure_one()
     res_partner_bank = self.env['res.partner.bank'].search([
         ('sanitized_acc_number', '=', sanitize_account_number(acc_number)),
         ('company_id', '=', self.company_id.id)
     ],
                                                            limit=1)
     if res_partner_bank:
         self.bank_account_id = res_partner_bank.id
     else:
         self.bank_account_id = self.env['res.partner.bank'].create({
             'acc_number':
             acc_number,
             'bank_id':
             bank_id,
             'company_id':
             self.company_id.id,
             'currency_id':
             self.currency_id.id,
             'partner_id':
             self.company_id.partner_id.id,
         }).id
Пример #20
0
 def _generate_unique_import_id(self, unique_import_id):
     self.ensure_one()
     sanitized_account_number = sanitize_account_number(self.account_number)
     return (sanitized_account_number and sanitized_account_number + '-'
             or '') + str(self.journal_id.id) + '-' + unique_import_id
Пример #21
0
 def _sanitize_bank_account_number(self, bank_account_number):
     """Hook for extension"""
     self.ensure_one()
     return sanitize_account_number(bank_account_number)
Пример #22
0
    def _onchange_set_l10n_ch_postal(self):
        try:
            validate_iban(self.bank_acc_number)
            is_iban = True
        except ValidationError:
            is_iban = False

        if is_iban:
            self.l10n_ch_postal = self.env['res.partner.bank']._retrieve_l10n_ch_postal(sanitize_account_number(self.bank_acc_number))
        else:
            self.l10n_ch_postal = self.bank_acc_number
Пример #23
0
    def build_swiss_code_url(self, amount, currency_name, not_used_anymore_1,
                             debtor_partner, not_used_anymore_2,
                             structured_communication, free_communication):
        comment = ""
        if free_communication:
            comment = (
                free_communication[:137] +
                '...') if len(free_communication) > 140 else free_communication

        creditor_addr_1, creditor_addr_2 = self._get_partner_address_lines(
            self.partner_id)
        debtor_addr_1, debtor_addr_2 = self._get_partner_address_lines(
            debtor_partner)

        # Compute reference type (empty by default, only mandatory for QR-IBAN,
        # and must then be 27 characters-long, with mod10r check digit as the 27th one,
        # just like ISR number for invoices)
        reference_type = 'NON'
        reference = ''
        if self._is_qr_iban():
            # _check_for_qr_code_errors ensures we can't have a QR-IBAN
            # without a QR-reference here
            reference_type = 'QRR'
            reference = structured_communication

        # If there is a QR IBAN we use it for the barcode instead of the account number
        acc_number = self.sanitized_acc_number
        if self.l10n_ch_qr_iban:
            acc_number = sanitize_account_number(self.l10n_ch_qr_iban)

        qr_code_vals = [
            'SPC',  # QR Type
            '0200',  # Version
            '1',  # Coding Type
            acc_number,  # IBAN
            'K',  # Creditor Address Type
            (self.acc_holder_name
             or self.partner_id.name)[:71],  # Creditor Name
            creditor_addr_1,  # Creditor Address Line 1
            creditor_addr_2,  # Creditor Address Line 2
            '',  # Creditor Postal Code (empty, since we're using combined addres elements)
            '',  # Creditor Town (empty, since we're using combined addres elements)
            self.partner_id.country_id.code,  # Creditor Country
            '',  # Ultimate Creditor Address Type
            '',  # Name
            '',  # Ultimate Creditor Address Line 1
            '',  # Ultimate Creditor Address Line 2
            '',  # Ultimate Creditor Postal Code
            '',  # Ultimate Creditor Town
            '',  # Ultimate Creditor Country
            '{:.2f}'.format(amount),  # Amount
            currency_name,  # Currency
            'K',  # Ultimate Debtor Address Type
            debtor_partner.name[:71],  # Ultimate Debtor Name
            debtor_addr_1,  # Ultimate Debtor Address Line 1
            debtor_addr_2,  # Ultimate Debtor Address Line 2
            '',  # Ultimate Debtor Postal Code (not to be provided for address type K)
            '',  # Ultimate Debtor Postal City (not to be provided for address type K)
            debtor_partner.country_id.code,  # Ultimate Debtor Postal Country
            reference_type,  # Reference Type
            reference,  # Reference
            comment,  # Unstructured Message
            'EPD',  # Mandatory trailer part
        ]

        return '/report/barcode/?type=%s&value=%s&width=%s&height=%s&humanreadable=1' % (
            'QR_quiet', werkzeug.urls.url_quote_plus(
                '\n'.join(qr_code_vals)), 256, 256)
Пример #24
0
    def _get_payload_params(self):
        self.ic_ref = self._get_ic_ref()
        bank_account = ""
        if self.payment_type == "qr":
            bank_account = sanitize_account_number(
                self.invoice_id.invoice_partner_bank_id.l10n_ch_qr_iban
                or self.invoice_id.invoice_partner_bank_id.acc_number)
        else:
            bank_account = (self.invoice_id.invoice_partner_bank_id.
                            l10n_ch_isr_subscription_chf)
            if bank_account:
                account_parts = bank_account.split("-")
                bank_account = (account_parts[0] +
                                account_parts[1].rjust(6, "0") +
                                account_parts[2])
            else:
                bank_account = ""

        params = {
            "client_pid": self.service_id.client_pid,
            "invoice": self.invoice_id,
            "invoice_lines": self.invoice_id.paynet_invoice_line_ids(),
            "biller": self.invoice_id.company_id,
            "customer": self.invoice_id.partner_id,
            "delivery": self.invoice_id.partner_shipping_id,
            "pdf_data": self.attachment_id.datas.decode("ascii"),
            "bank": self.invoice_id.invoice_partner_bank_id,
            "bank_account": bank_account,
            "ic_ref": self.ic_ref,
            "payment_type": self.payment_type,
            "document_type": DOCUMENT_TYPE[self.invoice_id.type],
            "format_date": self.format_date,
            "ebill_account_number": self.ebill_account_number,
            "discount_template": "",
            "discount": {},
        }
        amount_by_group = []
        # Get the percentage of the tax from the name of the group
        # Could be improve by searching in the account_tax linked to the group
        for taxgroup in self.invoice_id.amount_by_group:
            rate = taxgroup[0].split()[-1:][0][:-1]
            amount_by_group.append((
                rate or "0",
                taxgroup[1],
                taxgroup[2],
            ))
        params["amount_by_group"] = amount_by_group
        # Get the invoice due date
        date_due = None
        if self.invoice_id.invoice_payment_term_id:
            terms = self.invoice_id.invoice_payment_term_id.compute(
                self.invoice_id.amount_total)
            if terms:
                # Returns all payment and their date like [('2020-12-07', 430.37), ...]
                # Get the last payment date in the format "202021207"
                date_due = terms[-1][0].replace("-", "")
        if not date_due:
            date_due = self.format_date(self.invoice_id.invoice_date_due
                                        or self.invoice_id.invoice_date)
        params["date_due"] = date_due
        return params
Пример #25
0
 def retrieve_transactions(self):
     if (self.account_online_provider_id.provider_type != 'ponto'):
         return super(OnlineAccount, self).retrieve_transactions()
     # actualize the data in ponto
     # For some reason, ponto has 2 different routes to update the account balance and transactions
     # however if we try to refresh both one after another or at the same time, an error is received
     # An error is also received if we call their synchronization route too quickly. Therefore we
     # only refresh the transactions of the account and don't update the account which means that the
     # balance of the account won't be up-to-date. However this is not a big problem as the record that
     # store the balance is hidden for most user.
     self._ponto_synchronize('accountTransactions')
     self._ponto_synchronize('accountDetails')
     transactions = []
     # Update account balance
     url = '/accounts/%s' % (self.online_identifier, )
     resp_json = self.account_online_provider_id._ponto_fetch(
         'GET', url, {}, {})
     end_amount = resp_json.get('data',
                                {}).get('attributes',
                                        {}).get('currentBalance', 0)
     self.balance = end_amount
     # Fetch transactions.
     # Transactions are paginated so we need to loop to ensure we have every transactions, we keep
     # in memory the id of the last transaction fetched in order to start over from there.
     url = url + '/transactions'
     paging_forward = True
     if self.ponto_last_synchronization_identifier:
         paging_forward = False
         url = url + '?before=' + self.ponto_last_synchronization_identifier
     last_sync = fields.Date.to_date(
         (self.last_sync
          or fields.Datetime.now() - datetime.timedelta(days=15)))
     latest_transaction_identifier = False
     while url:
         resp_json = self.account_online_provider_id._ponto_fetch(
             'GET', url, {}, {})
         # 'prev' page contains newer transactions, 'next' page contains older ones.
         # we read from last known transaction to newer ones when we know such a transaction
         # else we read from the newest transaction back to our date limit
         url = resp_json.get('links',
                             {}).get('next' if paging_forward else 'prev',
                                     False)
         data_lines = resp_json.get('data', [])
         if data_lines:
             # latest transaction will be in the last page in backward direction, or in the first one in forward direction
             if ((not paging_forward and not url) or
                 (paging_forward and not latest_transaction_identifier)):
                 # a chunk sent by Ponto always has its most recent transaction first
                 latest_transaction_identifier = data_lines[0].get('id')
         for transaction in data_lines:
             # Convert received transaction datetime into Brussel timezone as we receive transaction date in an UTC
             # format but we store a date (which will loose information about the time) and some banks don't provide
             # the time of the transactions hence what we receive is a datetime in the form 2019-01-01T23:00:00.000z for a
             # transaction where the correct date should be of 2019-01-02
             # This is not the best fix as we should instead convert to the time of the country where the bank is located
             # but since ponto only support bank in belgium/france/nl for now this is acceptable.
             tr_date = dateutil.parser.parse(
                 transaction.get('attributes', {}).get('executionDate'))
             tr_date = tr_date.astimezone(GMT_BELGIUM)
             tr_date = fields.Date.to_date(tr_date)
             if paging_forward and tr_date < last_sync:
                 # Stop fetching transactions because we are paging forward
                 # and the following transactions are older than specified last_sync date.
                 url = False
                 break
             attributes = transaction.get('attributes', {})
             description = attributes.get('description') or ''
             counterpart = attributes.get('counterpartName') or ''
             remittanceinfo = attributes.get('remittanceInformation') or ''
             remittanceinfoType = attributes.get(
                 'remittanceInformationType') or ''
             name = ''
             if remittanceinfoType == 'structured':
                 name = remittanceinfo
             if not name:
                 name = ' '.join([description, counterpart, remittanceinfo
                                  ]) or '/'
             account_number = transaction.get(
                 'attributes', {}).get('counterpartReference')
             trans = {
                 'online_identifier': transaction.get('id'),
                 'date': tr_date,
                 'payment_ref': name,
                 'amount': transaction.get('attributes', {}).get('amount'),
                 'account_number': account_number,
             }
             if account_number:
                 company_id = self.account_online_provider_id.company_id.id
                 partner_bank = self.env['res.partner.bank'].search(
                     [('sanitized_acc_number', '=',
                       sanitize_account_number(account_number)), '|',
                      ('company_id', '=', company_id),
                      ('company_id', '=', False)],
                     order='company_id',
                     limit=1)
                 if partner_bank:
                     trans['partner_bank_id'] = partner_bank.id
                     trans['partner_id'] = partner_bank.partner_id.id
             if not trans.get('partner_id') and transaction.get(
                     'attributes', {}).get('counterpartName'):
                 trans['online_partner_vendor_name'] = transaction[
                     'attributes']['counterpartName']
                 trans['partner_id'] = self._find_partner([
                     ('online_partner_vendor_name', '=',
                      transaction['attributes']['counterpartName'])
                 ])
             transactions.append(trans)
     if latest_transaction_identifier:
         self.ponto_last_synchronization_identifier = latest_transaction_identifier
     # Create the bank statement with the transactions
     return self.env['account.bank.statement'].online_sync_bank_statement(
         transactions, self.journal_ids[0], end_amount)
Пример #26
0
    def _match_journal(self, account_number, currency):
        company = self.env.company
        journal_obj = self.env["account.journal"]
        if not account_number:  # exemple : QIF
            if not self.env.context.get("journal_id"):
                raise UserError(
                    _("The format of this bank statement file doesn't "
                      "contain the bank account number, so you must "
                      "start the wizard from the right bank journal "
                      "in the dashboard."))
            journal = journal_obj.browse(self.env.context.get("journal_id"))
        else:
            sanitized_account_number = sanitize_account_number(account_number)

            journal = journal_obj.search(
                [
                    ("type", "=", "bank"),
                    (
                        "bank_account_id.sanitized_acc_number",
                        "ilike",
                        sanitized_account_number,
                    ),
                ],
                limit=1,
            )

            if not journal:
                bank_accounts = self.env["res.partner.bank"].search(
                    [
                        ("partner_id", "=", company.partner_id.id),
                        ("sanitized_acc_number", "ilike",
                         sanitized_account_number),
                    ],
                    limit=1,
                )
                if bank_accounts:
                    raise UserError(
                        _("The bank account with number '%s' exists in Odoo "
                          "but it is not set on any bank journal. You should "
                          "set it on the related bank journal. If the related "
                          "bank journal doesn't exist yet, you should create "
                          "a new one.") % (account_number, ))
                else:
                    raise UserError(
                        _("Could not find any bank account with number '%s' "
                          "linked to partner '%s'. You should create the bank "
                          "account and set it on the related bank journal. "
                          "If the related bank journal doesn't exist yet, you "
                          "should create a new one.") %
                        (account_number, company.partner_id.display_name))

        # We support multi-file and multi-statement in a file
        # so self.env.context.get('journal_id') doesn't mean much
        # I don't think we should really use it
        journal_currency = journal.currency_id or company.currency_id
        if journal_currency != currency:
            raise UserError(
                _("The currency of the bank statement (%s) is not the same as the "
                  "currency of the journal '%s' (%s).") %
                (currency.name, journal.display_name, journal_currency.name))
        return journal
Пример #27
0
    def _l10n_ch_get_qr_vals(self, amount, currency, debtor_partner,
                             free_communication, structured_communication):
        comment = ""
        if free_communication:
            comment = (
                free_communication[:137] +
                '...') if len(free_communication) > 140 else free_communication

        creditor_addr_1, creditor_addr_2 = self._get_partner_address_lines(
            self.partner_id)
        debtor_addr_1, debtor_addr_2 = self._get_partner_address_lines(
            debtor_partner)

        # Compute reference type (empty by default, only mandatory for QR-IBAN,
        # and must then be 27 characters-long, with mod10r check digit as the 27th one,
        # just like ISR number for invoices)
        reference_type = 'NON'
        reference = ''
        acc_number = self.sanitized_acc_number

        if self.l10n_ch_qr_iban:
            # _check_for_qr_code_errors ensures we can't have a QR-IBAN without a QR-reference here
            reference_type = 'QRR'
            reference = structured_communication
            acc_number = sanitize_account_number(self.l10n_ch_qr_iban)

        currency = currency or self.currency_id or self.company_id.currency_id

        return [
            'SPC',  # QR Type
            '0200',  # Version
            '1',  # Coding Type
            acc_number,  # IBAN / QR-IBAN
            'K',  # Creditor Address Type
            (self.acc_holder_name
             or self.partner_id.name)[:70],  # Creditor Name
            creditor_addr_1,  # Creditor Address Line 1
            creditor_addr_2,  # Creditor Address Line 2
            '',  # Creditor Postal Code (empty, since we're using combined addres elements)
            '',  # Creditor Town (empty, since we're using combined addres elements)
            self.partner_id.country_id.code,  # Creditor Country
            '',  # Ultimate Creditor Address Type
            '',  # Name
            '',  # Ultimate Creditor Address Line 1
            '',  # Ultimate Creditor Address Line 2
            '',  # Ultimate Creditor Postal Code
            '',  # Ultimate Creditor Town
            '',  # Ultimate Creditor Country
            '{:.2f}'.format(amount),  # Amount
            currency.name,  # Currency
            'K',  # Ultimate Debtor Address Type
            debtor_partner.commercial_partner_id.
            name[:70],  # Ultimate Debtor Name
            debtor_addr_1,  # Ultimate Debtor Address Line 1
            debtor_addr_2,  # Ultimate Debtor Address Line 2
            '',  # Ultimate Debtor Postal Code (not to be provided for address type K)
            '',  # Ultimate Debtor Postal City (not to be provided for address type K)
            debtor_partner.country_id.code,  # Ultimate Debtor Postal Country
            reference_type,  # Reference Type
            reference,  # Reference
            comment,  # Unstructured Message
            'EPD',  # Mandatory trailer part
        ]