[IMP] account_bank_statement_import_split: black, isort, prettier

This commit is contained in:
Alexey Pelykh
2021-09-23 11:51:25 +02:00
parent 255022ecb2
commit d94e9040a5
6 changed files with 304 additions and 289 deletions

View File

@@ -3,21 +3,15 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{ {
'name': 'Account Bank Statement Import: Split by date', "name": "Account Bank Statement Import: Split by date",
'version': '12.0.1.0.1', "version": "12.0.1.0.1",
'author': "author": "CorporateHub, " "Odoo Community Association (OCA)",
'CorporateHub, ' "maintainers": ["alexey-pelykh"],
'Odoo Community Association (OCA)', "website": "https://github.com/OCA/bank-statement-import/",
'maintainers': ['alexey-pelykh'], "license": "AGPL-3",
'website': 'https://github.com/OCA/bank-statement-import/', "category": "Accounting",
'license': 'AGPL-3', "summary": "Split statements by date during import",
'category': 'Accounting', "depends": ["account_bank_statement_import",],
'summary': 'Split statements by date during import', "data": ["views/account_bank_statement_import.xml",],
'depends': [ "installable": True,
'account_bank_statement_import',
],
'data': [
'views/account_bank_statement_import.xml',
],
'installable': True,
} }

View File

@@ -1,54 +1,57 @@
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) # Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from dateutil.relativedelta import relativedelta, MO
from decimal import Decimal from decimal import Decimal
from dateutil.relativedelta import MO, relativedelta
from odoo import api, fields, models
class AccountBankStatementImport(models.TransientModel): class AccountBankStatementImport(models.TransientModel):
_inherit = 'account.bank.statement.import' _inherit = "account.bank.statement.import"
import_mode = fields.Selection( import_mode = fields.Selection(
selection=[ selection=[
('single', 'Single statement'), ("single", "Single statement"),
('daily', 'Daily statements'), ("daily", "Daily statements"),
('weekly', 'Weekly statements'), ("weekly", "Weekly statements"),
('monthly', 'Monthly statements'), ("monthly", "Monthly statements"),
], ],
default='single', default="single",
) )
def _complete_stmts_vals(self, stmts_vals, journal, account_number): def _complete_stmts_vals(self, stmts_vals, journal, account_number):
stmts_vals = super()._complete_stmts_vals( stmts_vals = super()._complete_stmts_vals(stmts_vals, journal, account_number)
stmts_vals, if not self.import_mode or self.import_mode == "single":
journal,
account_number
)
if not self.import_mode or self.import_mode == 'single':
return stmts_vals return stmts_vals
statements = [] statements = []
for st_vals in stmts_vals: for st_vals in stmts_vals:
transactions = list(sorted( transactions = list(
sorted(
map( map(
lambda transaction: self._prepare_transaction( lambda transaction: self._prepare_transaction(transaction),
transaction st_vals["transactions"],
), ),
st_vals['transactions'] key=lambda transaction: transaction["date"],
), )
key=lambda transaction: transaction['date'] )
))
if not transactions: if not transactions:
continue continue
del st_vals['transactions'] del st_vals["transactions"]
balance_start = Decimal(st_vals['balance_start']) \ balance_start = (
if 'balance_start' in st_vals else None Decimal(st_vals["balance_start"])
balance_end = Decimal(st_vals['balance_end_real']) \ if "balance_start" in st_vals
if 'balance_end_real' in st_vals else None else None
)
balance_end = (
Decimal(st_vals["balance_end_real"])
if "balance_end_real" in st_vals
else None
)
statement_date_since = self._get_statement_date_since( statement_date_since = self._get_statement_date_since(
transactions[0]['date'] transactions[0]["date"]
) )
while transactions: while transactions:
statement_date_until = ( statement_date_until = (
@@ -57,7 +60,7 @@ class AccountBankStatementImport(models.TransientModel):
last_transaction_index = None last_transaction_index = None
for index, transaction in enumerate(transactions): for index, transaction in enumerate(transactions):
if transaction['date'] >= statement_date_until: if transaction["date"] >= statement_date_until:
break break
last_transaction_index = index last_transaction_index = index
if last_transaction_index is None: if last_transaction_index is None:
@@ -65,36 +68,34 @@ class AccountBankStatementImport(models.TransientModel):
statement_date_since = statement_date_until statement_date_since = statement_date_until
continue continue
statement_transactions = \ statement_transactions = transactions[0 : last_transaction_index + 1]
transactions[0:last_transaction_index + 1]
transactions = transactions[last_transaction_index + 1 :] transactions = transactions[last_transaction_index + 1 :]
statement_values = dict(st_vals) statement_values = dict(st_vals)
statement_values.update({ statement_values.update(
'name': self._get_statement_name( {
journal, "name": self._get_statement_name(
statement_date_since, journal, statement_date_since, statement_date_until,
statement_date_until,
), ),
'date': self._get_statement_date( "date": self._get_statement_date(
statement_date_since, statement_date_since, statement_date_until,
statement_date_until,
), ),
'transactions': statement_transactions, "transactions": statement_transactions,
}) }
)
if balance_start is not None: if balance_start is not None:
statement_values.update({ statement_values.update(
'balance_start': float(balance_start), {"balance_start": float(balance_start),}
}) )
for transaction in statement_transactions: for transaction in statement_transactions:
balance_start += Decimal(transaction['amount']) balance_start += Decimal(transaction["amount"])
if balance_end is not None: if balance_end is not None:
statement_balance_end = balance_end statement_balance_end = balance_end
for transaction in transactions: for transaction in transactions:
statement_balance_end -= Decimal(transaction['amount']) statement_balance_end -= Decimal(transaction["amount"])
statement_values.update({ statement_values.update(
'balance_end_real': float(statement_balance_end), {"balance_end_real": float(statement_balance_end),}
}) )
statements.append(statement_values) statements.append(statement_values)
statement_date_since = statement_date_until statement_date_since = statement_date_until
@@ -102,40 +103,30 @@ class AccountBankStatementImport(models.TransientModel):
@api.multi @api.multi
def _prepare_transaction(self, transaction): def _prepare_transaction(self, transaction):
transaction.update({ transaction.update(
'date': fields.Date.from_string(transaction['date']), {"date": fields.Date.from_string(transaction["date"]),}
}) )
return transaction return transaction
@api.multi @api.multi
def _get_statement_date_since(self, date): def _get_statement_date_since(self, date):
self.ensure_one() self.ensure_one()
if self.import_mode == 'daily': if self.import_mode == "daily":
return date return date
elif self.import_mode == 'weekly': elif self.import_mode == "weekly":
return date + relativedelta(weekday=MO(-1)) return date + relativedelta(weekday=MO(-1))
elif self.import_mode == 'monthly': elif self.import_mode == "monthly":
return date.replace( return date.replace(day=1,)
day=1,
)
@api.multi @api.multi
def _get_statement_date_step(self): def _get_statement_date_step(self):
self.ensure_one() self.ensure_one()
if self.import_mode == 'daily': if self.import_mode == "daily":
return relativedelta( return relativedelta(days=1,)
days=1, elif self.import_mode == "weekly":
) return relativedelta(weeks=1, weekday=MO,)
elif self.import_mode == 'weekly': elif self.import_mode == "monthly":
return relativedelta( return relativedelta(months=1, day=1,)
weeks=1,
weekday=MO,
)
elif self.import_mode == 'monthly':
return relativedelta(
months=1,
day=1,
)
@api.multi @api.multi
def _get_statement_date(self, date_since, date_until): def _get_statement_date(self, date_since, date_until):

View File

@@ -1,159 +1,166 @@
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) # Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo.tests import common
from base64 import b64encode from base64 import b64encode
from unittest import mock from unittest import mock
from odoo import fields
from odoo.tests import common
_parse_file_method = ( _parse_file_method = (
'odoo.addons.account_bank_statement_import' "odoo.addons.account_bank_statement_import"
'.account_bank_statement_import.AccountBankStatementImport._parse_file' ".account_bank_statement_import.AccountBankStatementImport._parse_file"
) )
class TestAccountBankAccountStatementImportSplit(common.TransactionCase): class TestAccountBankAccountStatementImportSplit(common.TransactionCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.now = fields.Datetime.now() self.now = fields.Datetime.now()
self.currency_usd = self.env.ref('base.USD') self.currency_usd = self.env.ref("base.USD")
self.empty_data_file = b64encode( self.empty_data_file = b64encode(
'TestAccountBankAccountStatementImportSplit'.encode('utf-8') b"TestAccountBankAccountStatementImportSplit"
) )
self.AccountJournal = self.env['account.journal'] self.AccountJournal = self.env["account.journal"]
self.AccountBankStatement = self.env['account.bank.statement'] self.AccountBankStatement = self.env["account.bank.statement"]
self.AccountBankStatementImport = self.env[ self.AccountBankStatementImport = self.env["account.bank.statement.import"]
'account.bank.statement.import'
]
def test_default_import_mode(self): def test_default_import_mode(self):
journal = self.AccountJournal.create({ journal = self.AccountJournal.create(
'name': 'Bank', {
'type': 'bank', "name": "Bank",
'code': 'BANK', "type": "bank",
'currency_id': self.currency_usd.id, "code": "BANK",
}) "currency_id": self.currency_usd.id,
wizard = self.AccountBankStatementImport.with_context({ }
'journal_id': journal.id, )
}).create({ wizard = self.AccountBankStatementImport.with_context(
'filename': 'file.ext', {"journal_id": journal.id,}
'data_file': self.empty_data_file, ).create({"filename": "file.ext", "data_file": self.empty_data_file,})
})
data = ( data = (
journal.currency_id.name, journal.currency_id.name,
journal.bank_account_id.acc_number, journal.bank_account_id.acc_number,
[{ [
'name': 'STATEMENT', {
'date': '2019-01-01', "name": "STATEMENT",
'balance_start': 0.0, "date": "2019-01-01",
'balance_end_real': 100.0, "balance_start": 0.0,
'transactions': [{ "balance_end_real": 100.0,
'name': 'TRANSACTION', "transactions": [
'amount': '100.0', {
'date': '2019-01-01', "name": "TRANSACTION",
'note': 'NOTE', "amount": "100.0",
'unique_import_id': 'TRANSACTION-ID', "date": "2019-01-01",
}], "note": "NOTE",
}], "unique_import_id": "TRANSACTION-ID",
}
],
}
],
) )
with mock.patch(_parse_file_method, return_value=data): with mock.patch(_parse_file_method, return_value=data):
wizard.with_context({ wizard.with_context({"journal_id": journal.id,}).import_file()
'journal_id': journal.id, statement = self.AccountBankStatement.search([("journal_id", "=", journal.id),])
}).import_file()
statement = self.AccountBankStatement.search([
('journal_id', '=', journal.id),
])
self.assertEqual(len(statement), 1) self.assertEqual(len(statement), 1)
self.assertEqual(len(statement.line_ids), 1) self.assertEqual(len(statement.line_ids), 1)
def test_single_import_mode(self): def test_single_import_mode(self):
journal = self.AccountJournal.create({ journal = self.AccountJournal.create(
'name': 'Bank', {
'type': 'bank', "name": "Bank",
'code': 'BANK', "type": "bank",
'currency_id': self.currency_usd.id, "code": "BANK",
}) "currency_id": self.currency_usd.id,
wizard = self.AccountBankStatementImport.with_context({ }
'journal_id': journal.id, )
}).create({ wizard = self.AccountBankStatementImport.with_context(
'filename': 'file.ext', {"journal_id": journal.id,}
'data_file': self.empty_data_file, ).create(
'import_mode': 'single', {
}) "filename": "file.ext",
"data_file": self.empty_data_file,
"import_mode": "single",
}
)
data = ( data = (
journal.currency_id.name, journal.currency_id.name,
journal.bank_account_id.acc_number, journal.bank_account_id.acc_number,
[{ [
'name': 'STATEMENT', {
'date': '2019-01-01', "name": "STATEMENT",
'balance_start': 0.0, "date": "2019-01-01",
'balance_end_real': 100.0, "balance_start": 0.0,
'transactions': [{ "balance_end_real": 100.0,
'name': 'TRANSACTION', "transactions": [
'amount': '100.0', {
'date': '2019-01-01', "name": "TRANSACTION",
'note': 'NOTE', "amount": "100.0",
'unique_import_id': 'TRANSACTION-ID', "date": "2019-01-01",
}], "note": "NOTE",
}], "unique_import_id": "TRANSACTION-ID",
}
],
}
],
) )
with mock.patch(_parse_file_method, return_value=data): with mock.patch(_parse_file_method, return_value=data):
wizard.with_context({ wizard.with_context({"journal_id": journal.id,}).import_file()
'journal_id': journal.id, statement = self.AccountBankStatement.search([("journal_id", "=", journal.id),])
}).import_file()
statement = self.AccountBankStatement.search([
('journal_id', '=', journal.id),
])
self.assertEqual(len(statement), 1) self.assertEqual(len(statement), 1)
self.assertEqual(len(statement.line_ids), 1) self.assertEqual(len(statement.line_ids), 1)
def test_daily_import_mode(self): def test_daily_import_mode(self):
journal = self.AccountJournal.create({ journal = self.AccountJournal.create(
'name': 'Bank', {
'type': 'bank', "name": "Bank",
'code': 'BANK', "type": "bank",
'currency_id': self.currency_usd.id, "code": "BANK",
}) "currency_id": self.currency_usd.id,
wizard = self.AccountBankStatementImport.with_context({ }
'journal_id': journal.id, )
}).create({ wizard = self.AccountBankStatementImport.with_context(
'filename': 'file.ext', {"journal_id": journal.id,}
'data_file': self.empty_data_file, ).create(
'import_mode': 'daily', {
}) "filename": "file.ext",
"data_file": self.empty_data_file,
"import_mode": "daily",
}
)
data = ( data = (
journal.currency_id.name, journal.currency_id.name,
journal.bank_account_id.acc_number, journal.bank_account_id.acc_number,
[{ [
'name': 'STATEMENT', {
'date': '2019-01-01', "name": "STATEMENT",
'balance_start': 0.0, "date": "2019-01-01",
'balance_end_real': 100.0, "balance_start": 0.0,
'transactions': [{ "balance_end_real": 100.0,
'name': 'TRANSACTION-1', "transactions": [
'amount': '50.0', {
'date': '2019-01-01', "name": "TRANSACTION-1",
'note': 'NOTE', "amount": "50.0",
'unique_import_id': 'TRANSACTION-ID-1', "date": "2019-01-01",
}, { "note": "NOTE",
'name': 'TRANSACTION-2', "unique_import_id": "TRANSACTION-ID-1",
'amount': '50.0', },
'date': '2019-01-03', {
'note': 'NOTE', "name": "TRANSACTION-2",
'unique_import_id': 'TRANSACTION-ID-2', "amount": "50.0",
}], "date": "2019-01-03",
}], "note": "NOTE",
"unique_import_id": "TRANSACTION-ID-2",
},
],
}
],
) )
with mock.patch(_parse_file_method, return_value=data): with mock.patch(_parse_file_method, return_value=data):
wizard.with_context({ wizard.with_context({"journal_id": journal.id,}).import_file()
'journal_id': journal.id, statements = self.AccountBankStatement.search(
}).import_file() [("journal_id", "=", journal.id),]
statements = self.AccountBankStatement.search([ ).sorted(key=lambda statement: statement.date)
('journal_id', '=', journal.id),
]).sorted(key=lambda statement: statement.date)
self.assertEqual(len(statements), 2) self.assertEqual(len(statements), 2)
self.assertEqual(len(statements[0].line_ids), 1) self.assertEqual(len(statements[0].line_ids), 1)
self.assertEqual(statements[0].balance_start, 0.0) self.assertEqual(statements[0].balance_start, 0.0)
@@ -163,49 +170,56 @@ class TestAccountBankAccountStatementImportSplit(common.TransactionCase):
self.assertEqual(statements[1].balance_end_real, 100.0) self.assertEqual(statements[1].balance_end_real, 100.0)
def test_weekly_import_mode(self): def test_weekly_import_mode(self):
journal = self.AccountJournal.create({ journal = self.AccountJournal.create(
'name': 'Bank', {
'type': 'bank', "name": "Bank",
'code': 'BANK', "type": "bank",
'currency_id': self.currency_usd.id, "code": "BANK",
}) "currency_id": self.currency_usd.id,
wizard = self.AccountBankStatementImport.with_context({ }
'journal_id': journal.id, )
}).create({ wizard = self.AccountBankStatementImport.with_context(
'filename': 'file.ext', {"journal_id": journal.id,}
'data_file': self.empty_data_file, ).create(
'import_mode': 'weekly', {
}) "filename": "file.ext",
"data_file": self.empty_data_file,
"import_mode": "weekly",
}
)
data = ( data = (
journal.currency_id.name, journal.currency_id.name,
journal.bank_account_id.acc_number, journal.bank_account_id.acc_number,
[{ [
'name': 'STATEMENT', {
'date': '2019-01-01', "name": "STATEMENT",
'balance_start': 0.0, "date": "2019-01-01",
'balance_end_real': 100.0, "balance_start": 0.0,
'transactions': [{ "balance_end_real": 100.0,
'name': 'TRANSACTION-1', "transactions": [
'amount': '50.0', {
'date': '2019-01-01', "name": "TRANSACTION-1",
'note': 'NOTE', "amount": "50.0",
'unique_import_id': 'TRANSACTION-ID-1', "date": "2019-01-01",
}, { "note": "NOTE",
'name': 'TRANSACTION-2', "unique_import_id": "TRANSACTION-ID-1",
'amount': '50.0', },
'date': '2019-01-15', {
'note': 'NOTE', "name": "TRANSACTION-2",
'unique_import_id': 'TRANSACTION-ID-2', "amount": "50.0",
}], "date": "2019-01-15",
}], "note": "NOTE",
"unique_import_id": "TRANSACTION-ID-2",
},
],
}
],
) )
with mock.patch(_parse_file_method, return_value=data): with mock.patch(_parse_file_method, return_value=data):
wizard.with_context({ wizard.with_context({"journal_id": journal.id,}).import_file()
'journal_id': journal.id, statements = self.AccountBankStatement.search(
}).import_file() [("journal_id", "=", journal.id),]
statements = self.AccountBankStatement.search([ ).sorted(key=lambda statement: statement.date)
('journal_id', '=', journal.id),
]).sorted(key=lambda statement: statement.date)
self.assertEqual(len(statements), 2) self.assertEqual(len(statements), 2)
self.assertEqual(len(statements[0].line_ids), 1) self.assertEqual(len(statements[0].line_ids), 1)
self.assertEqual(statements[0].balance_start, 0.0) self.assertEqual(statements[0].balance_start, 0.0)
@@ -215,49 +229,56 @@ class TestAccountBankAccountStatementImportSplit(common.TransactionCase):
self.assertEqual(statements[1].balance_end_real, 100.0) self.assertEqual(statements[1].balance_end_real, 100.0)
def test_monthly_import_mode(self): def test_monthly_import_mode(self):
journal = self.AccountJournal.create({ journal = self.AccountJournal.create(
'name': 'Bank', {
'type': 'bank', "name": "Bank",
'code': 'BANK', "type": "bank",
'currency_id': self.currency_usd.id, "code": "BANK",
}) "currency_id": self.currency_usd.id,
wizard = self.AccountBankStatementImport.with_context({ }
'journal_id': journal.id, )
}).create({ wizard = self.AccountBankStatementImport.with_context(
'filename': 'file.ext', {"journal_id": journal.id,}
'data_file': self.empty_data_file, ).create(
'import_mode': 'monthly', {
}) "filename": "file.ext",
"data_file": self.empty_data_file,
"import_mode": "monthly",
}
)
data = ( data = (
journal.currency_id.name, journal.currency_id.name,
journal.bank_account_id.acc_number, journal.bank_account_id.acc_number,
[{ [
'name': 'STATEMENT', {
'date': '2019-01-01', "name": "STATEMENT",
'balance_start': 0.0, "date": "2019-01-01",
'balance_end_real': 100.0, "balance_start": 0.0,
'transactions': [{ "balance_end_real": 100.0,
'name': 'TRANSACTION-1', "transactions": [
'amount': '50.0', {
'date': '2019-01-01', "name": "TRANSACTION-1",
'note': 'NOTE', "amount": "50.0",
'unique_import_id': 'TRANSACTION-ID-1', "date": "2019-01-01",
}, { "note": "NOTE",
'name': 'TRANSACTION-2', "unique_import_id": "TRANSACTION-ID-1",
'amount': '50.0', },
'date': '2019-03-01', {
'note': 'NOTE', "name": "TRANSACTION-2",
'unique_import_id': 'TRANSACTION-ID-2', "amount": "50.0",
}], "date": "2019-03-01",
}], "note": "NOTE",
"unique_import_id": "TRANSACTION-ID-2",
},
],
}
],
) )
with mock.patch(_parse_file_method, return_value=data): with mock.patch(_parse_file_method, return_value=data):
wizard.with_context({ wizard.with_context({"journal_id": journal.id,}).import_file()
'journal_id': journal.id, statements = self.AccountBankStatement.search(
}).import_file() [("journal_id", "=", journal.id),]
statements = self.AccountBankStatement.search([ ).sorted(key=lambda statement: statement.date)
('journal_id', '=', journal.id),
]).sorted(key=lambda statement: statement.date)
self.assertEqual(len(statements), 2) self.assertEqual(len(statements), 2)
self.assertEqual(len(statements[0].line_ids), 1) self.assertEqual(len(statements[0].line_ids), 1)
self.assertEqual(statements[0].balance_start, 0.0) self.assertEqual(statements[0].balance_start, 0.0)

View File

@@ -4,16 +4,18 @@
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="account_bank_statement_import_view" model="ir.ui.view"> <record id="account_bank_statement_import_view" model="ir.ui.view">
<field name="model">account.bank.statement.import</field> <field name="model">account.bank.statement.import</field>
<field name="inherit_id" ref="account_bank_statement_import.account_bank_statement_import_view"/> <field
name="inherit_id"
ref="account_bank_statement_import.account_bank_statement_import_view"
/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//ul[@id='statement_format']" position="after"> <xpath expr="//ul[@id='statement_format']" position="after">
<p>Please select how you'd like to split the imported statement file:</p> <p
>Please select how you'd like to split the imported statement file:</p>
<field name="import_mode" widget="radio" required="1" /> <field name="import_mode" widget="radio" required="1" />
</xpath> </xpath>
</field> </field>
</record> </record>
</odoo> </odoo>

View File

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

View File

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