mirror of
https://github.com/OCA/bank-statement-import.git
synced 2025-01-20 12:37:43 +02:00
[IMP] : black, isort, prettier
This commit is contained in:
committed by
Ronald Portier (Therp BV)
parent
10d9639646
commit
fc8b6db62d
@@ -8,8 +8,6 @@
|
||||
"category": "Banking addons",
|
||||
"website": "https://opener.am",
|
||||
"license": "AGPL-3",
|
||||
"depends": [
|
||||
"account_cancel",
|
||||
],
|
||||
"depends": ["account_cancel",],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ from odoo import api, models
|
||||
|
||||
|
||||
class BankStatement(models.Model):
|
||||
_inherit = 'account.bank.statement'
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
@api.multi
|
||||
def get_reconcile_clearing_account_lines(self):
|
||||
@@ -16,23 +16,25 @@ class BankStatement(models.Model):
|
||||
the counterpart lines is zero.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if (self.journal_id.default_debit_account_id !=
|
||||
self.journal_id.default_credit_account_id or
|
||||
not self.journal_id.default_debit_account_id.reconcile):
|
||||
if (
|
||||
self.journal_id.default_debit_account_id
|
||||
!= self.journal_id.default_credit_account_id
|
||||
or not self.journal_id.default_debit_account_id.reconcile
|
||||
):
|
||||
return False
|
||||
account = self.journal_id.default_debit_account_id
|
||||
currency = self.journal_id.currency_id or self.company_id.currency_id
|
||||
|
||||
def get_bank_line(st_line):
|
||||
for line in st_line.journal_entry_ids:
|
||||
field = 'debit' if st_line.amount > 0 else 'credit'
|
||||
if (line.account_id == account and
|
||||
not currency.compare_amounts(
|
||||
line[field], abs(st_line.amount))):
|
||||
field = "debit" if st_line.amount > 0 else "credit"
|
||||
if line.account_id == account and not currency.compare_amounts(
|
||||
line[field], abs(st_line.amount)
|
||||
):
|
||||
return line
|
||||
return False
|
||||
|
||||
move_lines = self.env['account.move.line']
|
||||
move_lines = self.env["account.move.line"]
|
||||
for st_line in self.line_ids:
|
||||
bank_line = get_bank_line(st_line)
|
||||
if not bank_line:
|
||||
@@ -50,8 +52,8 @@ class BankStatement(models.Model):
|
||||
self.ensure_one()
|
||||
lines = self.get_reconcile_clearing_account_lines()
|
||||
if not lines or any(
|
||||
li.matched_debit_ids or li.matched_credit_ids
|
||||
for li in lines):
|
||||
li.matched_debit_ids or li.matched_credit_ids for li in lines
|
||||
):
|
||||
return False
|
||||
lines.reconcile()
|
||||
return True
|
||||
|
||||
@@ -12,13 +12,7 @@
|
||||
"account_bank_statement_import",
|
||||
"account_bank_statement_clearing_account",
|
||||
],
|
||||
"external_dependencies": {
|
||||
"python": [
|
||||
"openpyxl",
|
||||
],
|
||||
},
|
||||
"data": [
|
||||
"views/account_journal.xml",
|
||||
],
|
||||
"external_dependencies": {"python": ["openpyxl",],},
|
||||
"data": ["views/account_journal.xml",],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
# © 2017 Opener BV (<https://opener.amsterdam>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from io import BytesIO
|
||||
from openpyxl import load_workbook
|
||||
from zipfile import BadZipfile
|
||||
|
||||
from odoo import api, models, fields
|
||||
from openpyxl import load_workbook
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools.translate import _
|
||||
|
||||
|
||||
class AccountBankStatementImport(models.TransientModel):
|
||||
_inherit = 'account.bank.statement.import'
|
||||
_inherit = "account.bank.statement.import"
|
||||
|
||||
@api.model
|
||||
def _parse_file(self, data_file):
|
||||
@@ -19,41 +20,46 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
try:
|
||||
return self.import_adyen_xlsx(data_file)
|
||||
except ValueError:
|
||||
return super(AccountBankStatementImport, self)._parse_file(
|
||||
data_file)
|
||||
return super(AccountBankStatementImport, self)._parse_file(data_file)
|
||||
|
||||
def _find_additional_data(self, currency_code, account_number):
|
||||
""" Try to find journal by Adyen merchant account """
|
||||
if account_number:
|
||||
journal = self.env['account.journal'].search([
|
||||
('adyen_merchant_account', '=', account_number)], limit=1)
|
||||
journal = self.env["account.journal"].search(
|
||||
[("adyen_merchant_account", "=", account_number)], limit=1
|
||||
)
|
||||
if journal:
|
||||
if self._context.get('journal_id', journal.id) != journal.id:
|
||||
if self._context.get("journal_id", journal.id) != journal.id:
|
||||
raise UserError(
|
||||
_('Selected journal Merchant Account does not match '
|
||||
'the import file Merchant Account '
|
||||
'column: %s') % account_number)
|
||||
_(
|
||||
"Selected journal Merchant Account does not match "
|
||||
"the import file Merchant Account "
|
||||
"column: %s"
|
||||
)
|
||||
% account_number
|
||||
)
|
||||
self = self.with_context(journal_id=journal.id)
|
||||
return super(AccountBankStatementImport, self)._find_additional_data(
|
||||
currency_code, account_number)
|
||||
currency_code, account_number
|
||||
)
|
||||
|
||||
@api.model
|
||||
def balance(self, row):
|
||||
return -(row[15] or 0) + sum(
|
||||
row[i] if row[i] else 0.0
|
||||
for i in (16, 17, 18, 19, 20))
|
||||
row[i] if row[i] else 0.0 for i in (16, 17, 18, 19, 20)
|
||||
)
|
||||
|
||||
@api.model
|
||||
def import_adyen_transaction(self, statement, statement_id, row):
|
||||
transaction_id = str(len(statement['transactions'])).zfill(4)
|
||||
transaction_id = str(len(statement["transactions"])).zfill(4)
|
||||
transaction = dict(
|
||||
unique_import_id=statement_id + transaction_id,
|
||||
date=fields.Date.from_string(row[6]),
|
||||
amount=self.balance(row),
|
||||
note='%s %s %s %s' % (row[2], row[3], row[4], row[21]),
|
||||
note="{} {} {} {}".format(row[2], row[3], row[4], row[21]),
|
||||
name="%s" % (row[3] or row[4] or row[9]),
|
||||
)
|
||||
statement['transactions'].append(transaction)
|
||||
statement["transactions"].append(transaction)
|
||||
|
||||
@api.model
|
||||
def import_adyen_xlsx(self, data_file):
|
||||
@@ -75,61 +81,62 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
row = [cell.value for cell in row]
|
||||
if len(row) != 31:
|
||||
raise ValueError(
|
||||
'Not an Adyen statement. Unexpected row length %s '
|
||||
'instead of 31' % len(row))
|
||||
"Not an Adyen statement. Unexpected row length %s "
|
||||
"instead of 31" % len(row)
|
||||
)
|
||||
if not row[1]:
|
||||
continue
|
||||
if not headers:
|
||||
if row[1] != 'Company Account':
|
||||
if row[1] != "Company Account":
|
||||
raise ValueError(
|
||||
'Not an Adyen statement. Unexpected header "%s" '
|
||||
'instead of "Company Account"', row[1])
|
||||
'instead of "Company Account"',
|
||||
row[1],
|
||||
)
|
||||
headers = True
|
||||
continue
|
||||
if not statement:
|
||||
statement = {'transactions': []}
|
||||
statement = {"transactions": []}
|
||||
statements.append(statement)
|
||||
statement_id = '%s %s/%s' % (
|
||||
row[2], row[6].strftime('%Y'), int(row[23]))
|
||||
statement_id = "{} {}/{}".format(
|
||||
row[2],
|
||||
row[6].strftime("%Y"),
|
||||
int(row[23]),
|
||||
)
|
||||
currency_code = row[14]
|
||||
merchant_id = row[2]
|
||||
statement['name'] = '%s %s/%s' % (
|
||||
row[2], row[6].year, row[23])
|
||||
statement["name"] = "{} {}/{}".format(row[2], row[6].year, row[23])
|
||||
date = fields.Date.from_string(row[6])
|
||||
if not statement.get('date') or statement.get('date') > date:
|
||||
statement['date'] = date
|
||||
if not statement.get("date") or statement.get("date") > date:
|
||||
statement["date"] = date
|
||||
|
||||
row[8] = row[8].strip()
|
||||
if row[8] == 'MerchantPayout':
|
||||
if row[8] == "MerchantPayout":
|
||||
payout -= self.balance(row)
|
||||
else:
|
||||
balance += self.balance(row)
|
||||
self.import_adyen_transaction(statement, statement_id, row)
|
||||
fees += sum(
|
||||
row[i] if row[i] else 0.0
|
||||
for i in (17, 18, 19, 20))
|
||||
fees += sum(row[i] if row[i] else 0.0 for i in (17, 18, 19, 20))
|
||||
|
||||
if not headers:
|
||||
raise ValueError(
|
||||
'Not an Adyen statement. Did not encounter header row.')
|
||||
raise ValueError("Not an Adyen statement. Did not encounter header row.")
|
||||
|
||||
if fees:
|
||||
transaction_id = str(len(statement['transactions'])).zfill(4)
|
||||
transaction_id = str(len(statement["transactions"])).zfill(4)
|
||||
transaction = dict(
|
||||
unique_import_id=statement_id + transaction_id,
|
||||
date=max(t['date'] for t in statement['transactions']),
|
||||
date=max(t["date"] for t in statement["transactions"]),
|
||||
amount=-fees,
|
||||
name='Commission, markup etc. batch %s' % (int(row[23])),
|
||||
name="Commission, markup etc. batch %s" % (int(row[23])),
|
||||
)
|
||||
balance -= fees
|
||||
statement['transactions'].append(transaction)
|
||||
statement["transactions"].append(transaction)
|
||||
|
||||
if statement['transactions'] and not payout:
|
||||
if statement["transactions"] and not payout:
|
||||
raise UserError(_("No payout detected in Adyen statement."))
|
||||
if self.env.user.company_id.currency_id.compare_amounts(balance, payout) != 0:
|
||||
raise UserError(
|
||||
_('No payout detected in Adyen statement.'))
|
||||
if self.env.user.company_id.currency_id.compare_amounts(
|
||||
balance, payout) != 0:
|
||||
raise UserError(
|
||||
_('Parse error. Balance %s not equal to merchant '
|
||||
'payout %s') % (balance, payout))
|
||||
_("Parse error. Balance %s not equal to merchant " "payout %s")
|
||||
% (balance, payout)
|
||||
)
|
||||
return currency_code, merchant_id, statements
|
||||
|
||||
@@ -5,14 +5,16 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class Journal(models.Model):
|
||||
_inherit = 'account.journal'
|
||||
_inherit = "account.journal"
|
||||
|
||||
adyen_merchant_account = fields.Char(
|
||||
help=('Fill in the exact merchant account string to select this '
|
||||
'journal when importing Adyen statements'))
|
||||
help=(
|
||||
"Fill in the exact merchant account string to select this "
|
||||
"journal when importing Adyen statements"
|
||||
)
|
||||
)
|
||||
|
||||
def _get_bank_statements_available_import_formats(self):
|
||||
res = super(
|
||||
Journal, self)._get_bank_statements_available_import_formats()
|
||||
res.append('adyen')
|
||||
res = super(Journal, self)._get_bank_statements_available_import_formats()
|
||||
res.append("adyen")
|
||||
return res
|
||||
|
||||
@@ -3,24 +3,27 @@
|
||||
# © 2015 Therp BV (<http://therp.nl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
import base64
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import SavepointCase
|
||||
from odoo.modules.module import get_module_resource
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class TestImportAdyen(SavepointCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestImportAdyen, cls).setUpClass()
|
||||
cls.journal = cls.env['account.journal'].create({
|
||||
'company_id': cls.env.user.company_id.id,
|
||||
'name': 'Adyen test',
|
||||
'code': 'ADY',
|
||||
'type': 'bank',
|
||||
'adyen_merchant_account': 'YOURCOMPANY_ACCOUNT',
|
||||
'update_posted': True,
|
||||
'currency_id': cls.env.ref('base.USD').id,
|
||||
})
|
||||
cls.journal = cls.env["account.journal"].create(
|
||||
{
|
||||
"company_id": cls.env.user.company_id.id,
|
||||
"name": "Adyen test",
|
||||
"code": "ADY",
|
||||
"type": "bank",
|
||||
"adyen_merchant_account": "YOURCOMPANY_ACCOUNT",
|
||||
"update_posted": True,
|
||||
"currency_id": cls.env.ref("base.USD").id,
|
||||
}
|
||||
)
|
||||
# Enable reconcilation on the default journal account to trigger
|
||||
# the functionality from account_bank_statement_clearing_account
|
||||
cls.journal.default_debit_account_id.reconcile = True
|
||||
@@ -30,79 +33,91 @@ class TestImportAdyen(SavepointCase):
|
||||
lines on the default journal (clearing) account are fully reconciled
|
||||
with each other """
|
||||
self._test_statement_import(
|
||||
'account_bank_statement_import_adyen', 'adyen_test.xlsx',
|
||||
'YOURCOMPANY_ACCOUNT 2016/48')
|
||||
statement = self.env['account.bank.statement'].search(
|
||||
[], order='create_date desc', limit=1)
|
||||
"account_bank_statement_import_adyen",
|
||||
"adyen_test.xlsx",
|
||||
"YOURCOMPANY_ACCOUNT 2016/48",
|
||||
)
|
||||
statement = self.env["account.bank.statement"].search(
|
||||
[], order="create_date desc", limit=1
|
||||
)
|
||||
self.assertEqual(statement.journal_id, self.journal)
|
||||
self.assertEqual(len(statement.line_ids), 22)
|
||||
self.assertTrue(
|
||||
self.env.user.company_id.currency_id.is_zero(
|
||||
sum(line.amount for line in statement.line_ids)))
|
||||
sum(line.amount for line in statement.line_ids)
|
||||
)
|
||||
)
|
||||
|
||||
account = self.env['account.account'].search([(
|
||||
'internal_type', '=', 'receivable')], limit=1)
|
||||
account = self.env["account.account"].search(
|
||||
[("internal_type", "=", "receivable")], limit=1
|
||||
)
|
||||
for line in statement.line_ids:
|
||||
line.process_reconciliation(new_aml_dicts=[{
|
||||
'debit': -line.amount if line.amount < 0 else 0,
|
||||
'credit': line.amount if line.amount > 0 else 0,
|
||||
'account_id': account.id}])
|
||||
line.process_reconciliation(
|
||||
new_aml_dicts=[
|
||||
{
|
||||
"debit": -line.amount if line.amount < 0 else 0,
|
||||
"credit": line.amount if line.amount > 0 else 0,
|
||||
"account_id": account.id,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
statement.button_confirm_bank()
|
||||
self.assertEqual(statement.state, 'confirm')
|
||||
lines = self.env['account.move.line'].search([
|
||||
('account_id', '=', self.journal.default_debit_account_id.id),
|
||||
('statement_id', '=', statement.id)])
|
||||
reconcile = lines.mapped('full_reconcile_id')
|
||||
self.assertEqual(statement.state, "confirm")
|
||||
lines = self.env["account.move.line"].search(
|
||||
[
|
||||
("account_id", "=", self.journal.default_debit_account_id.id),
|
||||
("statement_id", "=", statement.id),
|
||||
]
|
||||
)
|
||||
reconcile = lines.mapped("full_reconcile_id")
|
||||
self.assertEqual(len(reconcile), 1)
|
||||
self.assertEqual(lines, reconcile.reconciled_line_ids)
|
||||
|
||||
# Reset the bank statement to see the counterpart lines being
|
||||
# unreconciled
|
||||
statement.button_draft()
|
||||
self.assertEqual(statement.state, 'open')
|
||||
self.assertFalse(lines.mapped('matched_debit_ids'))
|
||||
self.assertFalse(lines.mapped('matched_credit_ids'))
|
||||
self.assertFalse(lines.mapped('full_reconcile_id'))
|
||||
self.assertEqual(statement.state, "open")
|
||||
self.assertFalse(lines.mapped("matched_debit_ids"))
|
||||
self.assertFalse(lines.mapped("matched_credit_ids"))
|
||||
self.assertFalse(lines.mapped("full_reconcile_id"))
|
||||
|
||||
# Confirm the statement without the correct clearing account settings
|
||||
self.journal.default_debit_account_id.reconcile = False
|
||||
statement.button_confirm_bank()
|
||||
self.assertEqual(statement.state, 'confirm')
|
||||
self.assertFalse(lines.mapped('matched_debit_ids'))
|
||||
self.assertFalse(lines.mapped('matched_credit_ids'))
|
||||
self.assertFalse(lines.mapped('full_reconcile_id'))
|
||||
self.assertEqual(statement.state, "confirm")
|
||||
self.assertFalse(lines.mapped("matched_debit_ids"))
|
||||
self.assertFalse(lines.mapped("matched_credit_ids"))
|
||||
self.assertFalse(lines.mapped("full_reconcile_id"))
|
||||
|
||||
def test_02_import_adyen_credit_fees(self):
|
||||
""" Import an Adyen statement with credit fees """
|
||||
self._test_statement_import(
|
||||
'account_bank_statement_import_adyen',
|
||||
'adyen_test_credit_fees.xlsx',
|
||||
'YOURCOMPANY_ACCOUNT 2016/8')
|
||||
"account_bank_statement_import_adyen",
|
||||
"adyen_test_credit_fees.xlsx",
|
||||
"YOURCOMPANY_ACCOUNT 2016/8",
|
||||
)
|
||||
|
||||
def test_03_import_adyen_invalid(self):
|
||||
""" Trying to hit that coverall target """
|
||||
with self.assertRaisesRegex(UserError, 'Could not make sense'):
|
||||
with self.assertRaisesRegex(UserError, "Could not make sense"):
|
||||
self._test_statement_import(
|
||||
'account_bank_statement_import_adyen',
|
||||
'adyen_test_invalid.xls',
|
||||
'invalid')
|
||||
"account_bank_statement_import_adyen",
|
||||
"adyen_test_invalid.xls",
|
||||
"invalid",
|
||||
)
|
||||
|
||||
def _test_statement_import(
|
||||
self, module_name, file_name, statement_name):
|
||||
def _test_statement_import(self, module_name, file_name, statement_name):
|
||||
"""Test correct creation of single statement."""
|
||||
statement_path = get_module_resource(
|
||||
module_name,
|
||||
'test_files',
|
||||
file_name
|
||||
statement_path = get_module_resource(module_name, "test_files", file_name)
|
||||
statement_file = open(statement_path, "rb").read()
|
||||
import_wizard = self.env["account.bank.statement.import"].create(
|
||||
{"data_file": base64.b64encode(statement_file), "filename": file_name}
|
||||
)
|
||||
statement_file = open(statement_path, 'rb').read()
|
||||
import_wizard = self.env['account.bank.statement.import'].create({
|
||||
'data_file': base64.b64encode(statement_file),
|
||||
'filename': file_name})
|
||||
import_wizard.import_file()
|
||||
# statement name is account number + '-' + date of last line:
|
||||
statements = self.env['account.bank.statement'].search(
|
||||
[('name', '=', statement_name)])
|
||||
statements = self.env["account.bank.statement"].search(
|
||||
[("name", "=", statement_name)]
|
||||
)
|
||||
self.assertTrue(statements)
|
||||
return statements
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_account_journal_form" model="ir.ui.view">
|
||||
<field name="name">Add Adyen merchant account</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.view_account_journal_form"/>
|
||||
<field name="inherit_id" ref="account.view_account_journal_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="currency_id" position="after">
|
||||
<field name="adyen_merchant_account"/>
|
||||
<field name="adyen_merchant_account" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../../../account_bank_statement_clearing_account
|
||||
6
setup/account_bank_statement_clearing_account/setup.py
Normal file
6
setup/account_bank_statement_clearing_account/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
../../../../account_bank_statement_import_adyen
|
||||
6
setup/account_bank_statement_import_adyen/setup.py
Normal file
6
setup/account_bank_statement_import_adyen/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
Reference in New Issue
Block a user