111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import importlib
|
|
import importlib.util
|
|
import io
|
|
import logging
|
|
import re
|
|
import unicodedata
|
|
from xml.etree import ElementTree
|
|
|
|
from odoo import _, models
|
|
from odoo.exceptions import UserError
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
if not importlib.util.find_spec('ofxparse'):
|
|
_logger.warning("The ofxparse python library is not installed, ofx import will not work.")
|
|
|
|
|
|
class AccountJournal(models.Model):
|
|
_inherit = 'account.journal'
|
|
|
|
def _get_bank_statements_available_import_formats(self):
|
|
rslt = super(AccountJournal, self)._get_bank_statements_available_import_formats()
|
|
rslt.append('OFX')
|
|
return rslt
|
|
|
|
def _check_ofx(self, raw_file):
|
|
if (raw_file or b'').startswith(b"OFXHEADER"):
|
|
#v1 OFX
|
|
return True
|
|
try:
|
|
#v2 OFX
|
|
return b"<ofx>" in (raw_file or b'').lower()
|
|
except ElementTree.ParseError:
|
|
return False
|
|
|
|
def _fill_transaction_vals_line_ofx(self, transaction, length_transactions, partner_bank):
|
|
return {
|
|
'date': transaction.date,
|
|
'payment_ref': transaction.payee + (transaction.memo and ': ' + transaction.memo or ''),
|
|
'ref': transaction.id,
|
|
'amount': float(transaction.amount),
|
|
'unique_import_id': transaction.id,
|
|
'account_number': partner_bank.acc_number,
|
|
'partner_id': partner_bank.partner_id.id,
|
|
'sequence': length_transactions + 1,
|
|
}
|
|
|
|
def _parse_bank_statement_file(self, raw_file):
|
|
if not self._check_ofx(raw_file):
|
|
return super()._parse_bank_statement_file(raw_file)
|
|
try:
|
|
from odoo.addons.account_bank_statement_import_ofx.ofx_parser import OfxParser # noqa: PLC0415
|
|
except ImportError as e:
|
|
raise UserError(_("The library 'ofxparse' is missing, OFX import cannot proceed.")) from e
|
|
|
|
try:
|
|
ofx = OfxParser.parse(io.BytesIO(raw_file))
|
|
except UnicodeDecodeError:
|
|
# Replacing utf-8 chars with ascii equivalent
|
|
encoding = re.findall(rb'encoding="(.*?)"', raw_file)
|
|
encoding = encoding[0] if len(encoding) > 1 else 'utf-8'
|
|
try:
|
|
raw_file = unicodedata.normalize('NFKD', raw_file.decode(encoding)).encode('ascii', 'ignore')
|
|
ofx = OfxParser.parse(io.BytesIO(raw_file))
|
|
except UnicodeDecodeError:
|
|
raise UserError(_("There was an issue decoding the file. Please check the file encoding."))
|
|
vals_bank_statement = []
|
|
account_lst = set()
|
|
currency_lst = set()
|
|
# Since ofxparse doesn't provide account numbers, we'll have to find res.partner and res.partner.bank here
|
|
# (normal behaviour is to provide 'account_number', which the generic module uses to find partner/bank)
|
|
transaction_payees = [
|
|
transaction.payee
|
|
for account in ofx.accounts
|
|
for transaction in account.statement.transactions
|
|
]
|
|
partner_banks_dict = {
|
|
partner_bank.partner_id.name: partner_bank
|
|
for partner_bank in self.env['res.partner.bank'].search([
|
|
('partner_id.name', 'in', transaction_payees)
|
|
])
|
|
}
|
|
for account in ofx.accounts:
|
|
account_lst.add(account.number)
|
|
currency_lst.add(account.statement.currency)
|
|
transactions = []
|
|
total_amt = 0.00
|
|
for transaction in account.statement.transactions:
|
|
partner_bank = partner_banks_dict.get(transaction.payee, self.env['res.partner.bank'])
|
|
vals_line = self._fill_transaction_vals_line_ofx(transaction, len(transactions), partner_bank)
|
|
total_amt += float(transaction.amount)
|
|
transactions.append(vals_line)
|
|
|
|
vals_bank_statement.append({
|
|
'transactions': transactions,
|
|
# WARNING: the provided ledger balance is not necessarily the ending balance of the statement
|
|
# see https://github.com/odoo/odoo/issues/3003
|
|
'balance_start': float(account.statement.balance) - total_amt,
|
|
'balance_end_real': account.statement.balance,
|
|
})
|
|
|
|
if account_lst and len(account_lst) == 1:
|
|
account_lst = account_lst.pop()
|
|
currency_lst = currency_lst.pop()
|
|
else:
|
|
account_lst = None
|
|
currency_lst = None
|
|
|
|
return [[currency_lst, account_lst, vals_bank_statement]]
|