[IMP] : black, isort, prettier

This commit is contained in:
Ronald Portier
2021-02-17 16:26:15 +01:00
committed by Ronald Portier (Therp BV)
parent 10d9639646
commit fc8b6db62d
11 changed files with 161 additions and 129 deletions

View File

@@ -8,8 +8,6 @@
"category": "Banking addons", "category": "Banking addons",
"website": "https://opener.am", "website": "https://opener.am",
"license": "AGPL-3", "license": "AGPL-3",
"depends": [ "depends": ["account_cancel",],
"account_cancel",
],
"installable": True, "installable": True,
} }

View File

@@ -5,7 +5,7 @@ from odoo import api, models
class BankStatement(models.Model): class BankStatement(models.Model):
_inherit = 'account.bank.statement' _inherit = "account.bank.statement"
@api.multi @api.multi
def get_reconcile_clearing_account_lines(self): def get_reconcile_clearing_account_lines(self):
@@ -16,23 +16,25 @@ class BankStatement(models.Model):
the counterpart lines is zero. the counterpart lines is zero.
""" """
self.ensure_one() self.ensure_one()
if (self.journal_id.default_debit_account_id != if (
self.journal_id.default_credit_account_id or self.journal_id.default_debit_account_id
not self.journal_id.default_debit_account_id.reconcile): != self.journal_id.default_credit_account_id
or not self.journal_id.default_debit_account_id.reconcile
):
return False return False
account = self.journal_id.default_debit_account_id account = self.journal_id.default_debit_account_id
currency = self.journal_id.currency_id or self.company_id.currency_id currency = self.journal_id.currency_id or self.company_id.currency_id
def get_bank_line(st_line): def get_bank_line(st_line):
for line in st_line.journal_entry_ids: for line in st_line.journal_entry_ids:
field = 'debit' if st_line.amount > 0 else 'credit' field = "debit" if st_line.amount > 0 else "credit"
if (line.account_id == account and if line.account_id == account and not currency.compare_amounts(
not currency.compare_amounts( line[field], abs(st_line.amount)
line[field], abs(st_line.amount))): ):
return line return line
return False return False
move_lines = self.env['account.move.line'] move_lines = self.env["account.move.line"]
for st_line in self.line_ids: for st_line in self.line_ids:
bank_line = get_bank_line(st_line) bank_line = get_bank_line(st_line)
if not bank_line: if not bank_line:
@@ -50,8 +52,8 @@ class BankStatement(models.Model):
self.ensure_one() self.ensure_one()
lines = self.get_reconcile_clearing_account_lines() lines = self.get_reconcile_clearing_account_lines()
if not lines or any( if not lines or any(
li.matched_debit_ids or li.matched_credit_ids li.matched_debit_ids or li.matched_credit_ids for li in lines
for li in lines): ):
return False return False
lines.reconcile() lines.reconcile()
return True return True

View File

@@ -12,13 +12,7 @@
"account_bank_statement_import", "account_bank_statement_import",
"account_bank_statement_clearing_account", "account_bank_statement_clearing_account",
], ],
"external_dependencies": { "external_dependencies": {"python": ["openpyxl",],},
"python": [ "data": ["views/account_journal.xml",],
"openpyxl",
],
},
"data": [
"views/account_journal.xml",
],
"installable": True, "installable": True,
} }

View File

@@ -1,16 +1,17 @@
# © 2017 Opener BV (<https://opener.amsterdam>) # © 2017 Opener BV (<https://opener.amsterdam>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from io import BytesIO from io import BytesIO
from openpyxl import load_workbook
from zipfile import BadZipfile 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.exceptions import UserError
from odoo.tools.translate import _ from odoo.tools.translate import _
class AccountBankStatementImport(models.TransientModel): class AccountBankStatementImport(models.TransientModel):
_inherit = 'account.bank.statement.import' _inherit = "account.bank.statement.import"
@api.model @api.model
def _parse_file(self, data_file): def _parse_file(self, data_file):
@@ -19,41 +20,46 @@ class AccountBankStatementImport(models.TransientModel):
try: try:
return self.import_adyen_xlsx(data_file) return self.import_adyen_xlsx(data_file)
except ValueError: except ValueError:
return super(AccountBankStatementImport, self)._parse_file( return super(AccountBankStatementImport, self)._parse_file(data_file)
data_file)
def _find_additional_data(self, currency_code, account_number): def _find_additional_data(self, currency_code, account_number):
""" Try to find journal by Adyen merchant account """ """ Try to find journal by Adyen merchant account """
if account_number: if account_number:
journal = self.env['account.journal'].search([ journal = self.env["account.journal"].search(
('adyen_merchant_account', '=', account_number)], limit=1) [("adyen_merchant_account", "=", account_number)], limit=1
)
if journal: if journal:
if self._context.get('journal_id', journal.id) != journal.id: if self._context.get("journal_id", journal.id) != journal.id:
raise UserError( raise UserError(
_('Selected journal Merchant Account does not match ' _(
'the import file Merchant Account ' "Selected journal Merchant Account does not match "
'column: %s') % account_number) "the import file Merchant Account "
"column: %s"
)
% account_number
)
self = self.with_context(journal_id=journal.id) self = self.with_context(journal_id=journal.id)
return super(AccountBankStatementImport, self)._find_additional_data( return super(AccountBankStatementImport, self)._find_additional_data(
currency_code, account_number) currency_code, account_number
)
@api.model @api.model
def balance(self, row): def balance(self, row):
return -(row[15] or 0) + sum( return -(row[15] or 0) + sum(
row[i] if row[i] else 0.0 row[i] if row[i] else 0.0 for i in (16, 17, 18, 19, 20)
for i in (16, 17, 18, 19, 20)) )
@api.model @api.model
def import_adyen_transaction(self, statement, statement_id, row): 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( transaction = dict(
unique_import_id=statement_id + transaction_id, unique_import_id=statement_id + transaction_id,
date=fields.Date.from_string(row[6]), date=fields.Date.from_string(row[6]),
amount=self.balance(row), 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]), name="%s" % (row[3] or row[4] or row[9]),
) )
statement['transactions'].append(transaction) statement["transactions"].append(transaction)
@api.model @api.model
def import_adyen_xlsx(self, data_file): def import_adyen_xlsx(self, data_file):
@@ -75,61 +81,62 @@ class AccountBankStatementImport(models.TransientModel):
row = [cell.value for cell in row] row = [cell.value for cell in row]
if len(row) != 31: if len(row) != 31:
raise ValueError( raise ValueError(
'Not an Adyen statement. Unexpected row length %s ' "Not an Adyen statement. Unexpected row length %s "
'instead of 31' % len(row)) "instead of 31" % len(row)
)
if not row[1]: if not row[1]:
continue continue
if not headers: if not headers:
if row[1] != 'Company Account': if row[1] != "Company Account":
raise ValueError( raise ValueError(
'Not an Adyen statement. Unexpected header "%s" ' 'Not an Adyen statement. Unexpected header "%s" '
'instead of "Company Account"', row[1]) 'instead of "Company Account"',
row[1],
)
headers = True headers = True
continue continue
if not statement: if not statement:
statement = {'transactions': []} statement = {"transactions": []}
statements.append(statement) statements.append(statement)
statement_id = '%s %s/%s' % ( statement_id = "{} {}/{}".format(
row[2], row[6].strftime('%Y'), int(row[23])) row[2],
row[6].strftime("%Y"),
int(row[23]),
)
currency_code = row[14] currency_code = row[14]
merchant_id = row[2] merchant_id = row[2]
statement['name'] = '%s %s/%s' % ( statement["name"] = "{} {}/{}".format(row[2], row[6].year, row[23])
row[2], row[6].year, row[23])
date = fields.Date.from_string(row[6]) date = fields.Date.from_string(row[6])
if not statement.get('date') or statement.get('date') > date: if not statement.get("date") or statement.get("date") > date:
statement['date'] = date statement["date"] = date
row[8] = row[8].strip() row[8] = row[8].strip()
if row[8] == 'MerchantPayout': if row[8] == "MerchantPayout":
payout -= self.balance(row) payout -= self.balance(row)
else: else:
balance += self.balance(row) balance += self.balance(row)
self.import_adyen_transaction(statement, statement_id, row) self.import_adyen_transaction(statement, statement_id, row)
fees += sum( fees += sum(row[i] if row[i] else 0.0 for i in (17, 18, 19, 20))
row[i] if row[i] else 0.0
for i in (17, 18, 19, 20))
if not headers: if not headers:
raise ValueError( raise ValueError("Not an Adyen statement. Did not encounter header row.")
'Not an Adyen statement. Did not encounter header row.')
if fees: if fees:
transaction_id = str(len(statement['transactions'])).zfill(4) transaction_id = str(len(statement["transactions"])).zfill(4)
transaction = dict( transaction = dict(
unique_import_id=statement_id + transaction_id, 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, amount=-fees,
name='Commission, markup etc. batch %s' % (int(row[23])), name="Commission, markup etc. batch %s" % (int(row[23])),
) )
balance -= fees 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( raise UserError(
_('No payout detected in Adyen statement.')) _("Parse error. Balance %s not equal to merchant " "payout %s")
if self.env.user.company_id.currency_id.compare_amounts( % (balance, payout)
balance, payout) != 0: )
raise UserError(
_('Parse error. Balance %s not equal to merchant '
'payout %s') % (balance, payout))
return currency_code, merchant_id, statements return currency_code, merchant_id, statements

View File

@@ -5,14 +5,16 @@ from odoo import fields, models
class Journal(models.Model): class Journal(models.Model):
_inherit = 'account.journal' _inherit = "account.journal"
adyen_merchant_account = fields.Char( adyen_merchant_account = fields.Char(
help=('Fill in the exact merchant account string to select this ' help=(
'journal when importing Adyen statements')) "Fill in the exact merchant account string to select this "
"journal when importing Adyen statements"
)
)
def _get_bank_statements_available_import_formats(self): def _get_bank_statements_available_import_formats(self):
res = super( res = super(Journal, self)._get_bank_statements_available_import_formats()
Journal, self)._get_bank_statements_available_import_formats() res.append("adyen")
res.append('adyen')
return res return res

View File

@@ -3,24 +3,27 @@
# © 2015 Therp BV (<http://therp.nl>) # © 2015 Therp BV (<http://therp.nl>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64 import base64
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tests.common import SavepointCase
from odoo.modules.module import get_module_resource from odoo.modules.module import get_module_resource
from odoo.tests.common import SavepointCase
class TestImportAdyen(SavepointCase): class TestImportAdyen(SavepointCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TestImportAdyen, cls).setUpClass() super(TestImportAdyen, cls).setUpClass()
cls.journal = cls.env['account.journal'].create({ cls.journal = cls.env["account.journal"].create(
'company_id': cls.env.user.company_id.id, {
'name': 'Adyen test', "company_id": cls.env.user.company_id.id,
'code': 'ADY', "name": "Adyen test",
'type': 'bank', "code": "ADY",
'adyen_merchant_account': 'YOURCOMPANY_ACCOUNT', "type": "bank",
'update_posted': True, "adyen_merchant_account": "YOURCOMPANY_ACCOUNT",
'currency_id': cls.env.ref('base.USD').id, "update_posted": True,
}) "currency_id": cls.env.ref("base.USD").id,
}
)
# Enable reconcilation on the default journal account to trigger # Enable reconcilation on the default journal account to trigger
# the functionality from account_bank_statement_clearing_account # the functionality from account_bank_statement_clearing_account
cls.journal.default_debit_account_id.reconcile = True 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 lines on the default journal (clearing) account are fully reconciled
with each other """ with each other """
self._test_statement_import( self._test_statement_import(
'account_bank_statement_import_adyen', 'adyen_test.xlsx', "account_bank_statement_import_adyen",
'YOURCOMPANY_ACCOUNT 2016/48') "adyen_test.xlsx",
statement = self.env['account.bank.statement'].search( "YOURCOMPANY_ACCOUNT 2016/48",
[], order='create_date desc', limit=1) )
statement = self.env["account.bank.statement"].search(
[], order="create_date desc", limit=1
)
self.assertEqual(statement.journal_id, self.journal) self.assertEqual(statement.journal_id, self.journal)
self.assertEqual(len(statement.line_ids), 22) self.assertEqual(len(statement.line_ids), 22)
self.assertTrue( self.assertTrue(
self.env.user.company_id.currency_id.is_zero( 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([( account = self.env["account.account"].search(
'internal_type', '=', 'receivable')], limit=1) [("internal_type", "=", "receivable")], limit=1
)
for line in statement.line_ids: for line in statement.line_ids:
line.process_reconciliation(new_aml_dicts=[{ line.process_reconciliation(
'debit': -line.amount if line.amount < 0 else 0, new_aml_dicts=[
'credit': line.amount if line.amount > 0 else 0, {
'account_id': account.id}]) "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() statement.button_confirm_bank()
self.assertEqual(statement.state, 'confirm') self.assertEqual(statement.state, "confirm")
lines = self.env['account.move.line'].search([ lines = self.env["account.move.line"].search(
('account_id', '=', self.journal.default_debit_account_id.id), [
('statement_id', '=', statement.id)]) ("account_id", "=", self.journal.default_debit_account_id.id),
reconcile = lines.mapped('full_reconcile_id') ("statement_id", "=", statement.id),
]
)
reconcile = lines.mapped("full_reconcile_id")
self.assertEqual(len(reconcile), 1) self.assertEqual(len(reconcile), 1)
self.assertEqual(lines, reconcile.reconciled_line_ids) self.assertEqual(lines, reconcile.reconciled_line_ids)
# Reset the bank statement to see the counterpart lines being # Reset the bank statement to see the counterpart lines being
# unreconciled # unreconciled
statement.button_draft() statement.button_draft()
self.assertEqual(statement.state, 'open') self.assertEqual(statement.state, "open")
self.assertFalse(lines.mapped('matched_debit_ids')) self.assertFalse(lines.mapped("matched_debit_ids"))
self.assertFalse(lines.mapped('matched_credit_ids')) self.assertFalse(lines.mapped("matched_credit_ids"))
self.assertFalse(lines.mapped('full_reconcile_id')) self.assertFalse(lines.mapped("full_reconcile_id"))
# Confirm the statement without the correct clearing account settings # Confirm the statement without the correct clearing account settings
self.journal.default_debit_account_id.reconcile = False self.journal.default_debit_account_id.reconcile = False
statement.button_confirm_bank() statement.button_confirm_bank()
self.assertEqual(statement.state, 'confirm') self.assertEqual(statement.state, "confirm")
self.assertFalse(lines.mapped('matched_debit_ids')) self.assertFalse(lines.mapped("matched_debit_ids"))
self.assertFalse(lines.mapped('matched_credit_ids')) self.assertFalse(lines.mapped("matched_credit_ids"))
self.assertFalse(lines.mapped('full_reconcile_id')) self.assertFalse(lines.mapped("full_reconcile_id"))
def test_02_import_adyen_credit_fees(self): def test_02_import_adyen_credit_fees(self):
""" Import an Adyen statement with credit fees """ """ Import an Adyen statement with credit fees """
self._test_statement_import( self._test_statement_import(
'account_bank_statement_import_adyen', "account_bank_statement_import_adyen",
'adyen_test_credit_fees.xlsx', "adyen_test_credit_fees.xlsx",
'YOURCOMPANY_ACCOUNT 2016/8') "YOURCOMPANY_ACCOUNT 2016/8",
)
def test_03_import_adyen_invalid(self): def test_03_import_adyen_invalid(self):
""" Trying to hit that coverall target """ """ 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( self._test_statement_import(
'account_bank_statement_import_adyen', "account_bank_statement_import_adyen",
'adyen_test_invalid.xls', "adyen_test_invalid.xls",
'invalid') "invalid",
)
def _test_statement_import( def _test_statement_import(self, module_name, file_name, statement_name):
self, module_name, file_name, statement_name):
"""Test correct creation of single statement.""" """Test correct creation of single statement."""
statement_path = get_module_resource( statement_path = get_module_resource(module_name, "test_files", file_name)
module_name, statement_file = open(statement_path, "rb").read()
'test_files', import_wizard = self.env["account.bank.statement.import"].create(
file_name {"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() import_wizard.import_file()
# statement name is account number + '-' + date of last line: # statement name is account number + '-' + date of last line:
statements = self.env['account.bank.statement'].search( statements = self.env["account.bank.statement"].search(
[('name', '=', statement_name)]) [("name", "=", statement_name)]
)
self.assertTrue(statements) self.assertTrue(statements)
return statements return statements

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_account_journal_form" model="ir.ui.view"> <record id="view_account_journal_form" model="ir.ui.view">
<field name="name">Add Adyen merchant account</field> <field name="name">Add Adyen merchant account</field>
<field name="model">account.journal</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="arch" type="xml">
<field name="currency_id" position="after"> <field name="currency_id" position="after">
<field name="adyen_merchant_account"/> <field name="adyen_merchant_account" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -0,0 +1 @@
../../../../account_bank_statement_clearing_account

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@@ -0,0 +1 @@
../../../../account_bank_statement_import_adyen

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)