Merge PR #569 into 15.0

Signed-off-by JordiBForgeFlow
This commit is contained in:
OCA-git-bot
2023-02-23 11:49:31 +00:00
15 changed files with 395 additions and 150 deletions

View File

@@ -11,7 +11,6 @@
"license": "AGPL-3",
"category": "Accounting",
"summary": "Online bank statements update",
"external_dependencies": {"python": ["odoo_test_helper"]},
"depends": [
"account",
"account_statement_import",

View File

@@ -17,7 +17,7 @@
"multi_step_wizard",
"web_widget_dropdown_dynamic",
],
"external_dependencies": {"python": ["xlrd"]},
"external_dependencies": {"python": ["xlrd", "chardet"]},
"data": [
"security/ir.model.access.csv",
"data/map_data.xml",

View File

@@ -1,40 +0,0 @@
# Copyright 2021 Tecnativa - Carlos Roca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from openupgradelib import openupgrade
_model_renames = [
(
"account.bank.statement.import.sheet.mapping",
"account.statement.import.sheet.mapping",
),
(
"account.bank.statement.import.sheet.parser",
"account.statement.import.sheet.parser",
),
(
"account.bank.statement.import.sheet.mapping.wizard",
"account.statement.import.sheet.mapping.wizard",
),
]
_table_renames = [
(
"account_bank_statement_import_sheet_mapping",
"account_statement_import_sheet_mapping",
),
(
"account_bank_statement_import_sheet_parser",
"account_statement_import_sheet_parser",
),
(
"account_bank_statement_import_sheet_mapping_wizard",
"account_statement_import_sheet_mapping_wizard",
),
]
@openupgrade.migrate()
def migrate(env, version):
openupgrade.rename_models(env.cr, _model_renames)
openupgrade.rename_tables(env.cr, _table_renames)

View File

@@ -0,0 +1,42 @@
# Copyright 2022 AppsToGROW - Henrik Norlin
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from openupgradelib import openupgrade
_fields_to_add = [
(
"amount_debit_column",
"account.statement.import.sheet.mapping",
"account_statement_import_sheet_mapping",
"char",
"varchar",
"account_statement_import_txt_xlsx",
),
(
"amount_credit_column",
"account.statement.import.sheet.mapping",
"account_statement_import_sheet_mapping",
"char",
"varchar",
"account_statement_import_txt_xlsx",
),
]
def add_fields_and_drop_not_null(env):
cr = env.cr
sql_debit_exists = """SELECT count(id) FROM ir_model_fields
WHERE name = 'amount_debit_column'
AND model = 'account.statement.import.sheet.mapping';"""
cr.execute(sql_debit_exists)
if cr.fetchone()[0] > 0:
openupgrade.add_fields(env, _fields_to_add)
cr.execute(
"""ALTER TABLE account_statement_import_sheet_mapping
ALTER COLUMN amount_column DROP NOT NULL;"""
)
@openupgrade.migrate()
def migrate(env, version):
add_fields_and_drop_not_null(env)

View File

@@ -28,9 +28,21 @@ class AccountStatementImport(models.TransientModel):
self.ensure_one()
try:
Parser = self.env["account.statement.import.sheet.parser"]
return Parser.parse(data_file, self.sheet_mapping_id)
return Parser.parse(
data_file, self.sheet_mapping_id, self.statement_filename
)
except BaseException:
if self.env.context.get("account_statement_import_txt_xlsx_test"):
raise
_logger.warning("Sheet parser error", exc_info=True)
return super()._parse_file(data_file)
def _create_bank_statements(self, stmts_vals, result):
"""Set balance_end_real if not already provided by the file."""
res = super()._create_bank_statements(stmts_vals, result)
statements = self.env["account.bank.statement"].browse(result["statement_ids"])
for statement in statements:
if not statement.balance_end_real:
amount = sum(statement.line_ids.mapped("amount"))
statement.balance_end_real = statement.balance_start + amount
return res

View File

@@ -55,6 +55,11 @@ class AccountStatementImportSheetMapping(models.Model):
)
quotechar = fields.Char(string="Text qualifier", size=1, default='"')
timestamp_format = fields.Char(required=True)
no_header = fields.Boolean(
string="File does not contain header line",
help="When this occurs please indicate the column number in the Columns section "
"instead of the column name, considering that the first column is 0",
)
timestamp_column = fields.Char(required=True)
currency_column = fields.Char(
help=(
@@ -63,9 +68,16 @@ class AccountStatementImportSheetMapping(models.Model):
),
)
amount_column = fields.Char(
required=True,
help="Amount of transaction in journal's currency",
)
amount_debit_column = fields.Char(
string="Debit amount column",
help="Debit amount of transaction in journal's currency",
)
amount_credit_column = fields.Char(
string="Credit amount column",
help="Credit amount of transaction in journal's currency",
)
balance_column = fields.Char(
help="Balance after transaction in journal's currency",
)
@@ -112,6 +124,19 @@ class AccountStatementImportSheetMapping(models.Model):
help="Partner's bank account",
)
_sql_constraints = [
(
"check_amount_columns",
(
"CHECK("
"amount_column IS NULL "
"OR (amount_debit_column IS NULL AND amount_credit_column IS NULL)"
")"
),
"Use amount_column OR (amount_debit_column AND amount_credit_column).",
),
]
@api.onchange("float_thousands_sep")
def onchange_thousands_separator(self):
if "dot" == self.float_thousands_sep == self.float_decimal_sep:

View File

@@ -7,8 +7,10 @@ import logging
from datetime import datetime
from decimal import Decimal
from io import StringIO
from os import path
from odoo import _, api, models
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
@@ -20,6 +22,14 @@ try:
except (ImportError, IOError) as err: # pragma: no cover
_logger.error(err)
try:
import chardet
except ImportError:
_logger.warning(
"chardet library not found, please install it "
"from http://pypi.python.org/pypi/chardet"
)
class AccountStatementImportSheetParser(models.TransientModel):
_name = "account.statement.import.sheet.parser"
@@ -43,7 +53,7 @@ class AccountStatementImportSheetParser(models.TransientModel):
return list(next(csv_data))
@api.model
def parse(self, data_file, mapping):
def parse(self, data_file, mapping, filename):
journal = self.env["account.journal"].browse(self.env.context.get("journal_id"))
currency_code = (journal.currency_id or journal.company_id.currency_id).name
account_number = journal.bank_account_id.acc_number
@@ -57,6 +67,11 @@ class AccountStatementImportSheetParser(models.TransientModel):
last_line = lines[-1]
data = {
"date": first_line["timestamp"].date(),
"name": _("%(code)s: %(filename)s")
% {
"code": journal.code,
"filename": path.basename(filename),
},
}
if mapping.balance_column:
@@ -69,7 +84,6 @@ class AccountStatementImportSheetParser(models.TransientModel):
"balance_end_real": float(balance_end),
}
)
transactions = list(
itertools.chain.from_iterable(
map(lambda line: self._convert_line_to_transactions(line), lines)
@@ -79,6 +93,50 @@ class AccountStatementImportSheetParser(models.TransientModel):
return currency_code, account_number, [data]
def _get_column_indexes(self, header, column_name, mapping):
column_indexes = []
if mapping[column_name] and "," in mapping[column_name]:
# We have to concatenate the values
column_names_or_indexes = mapping[column_name].split(",")
else:
column_names_or_indexes = [mapping[column_name]]
for column_name_or_index in column_names_or_indexes:
if not column_name_or_index:
continue
column_index = None
if mapping.no_header:
try:
column_index = int(column_name_or_index)
# pylint: disable=except-pass
except Exception:
pass
if column_index is not None:
column_indexes.append(column_index)
else:
if column_name_or_index:
column_indexes.append(header.index(column_name_or_index))
return column_indexes
def _get_column_names(self):
return [
"timestamp_column",
"currency_column",
"amount_column",
"amount_debit_column",
"amount_credit_column",
"balance_column",
"original_currency_column",
"original_amount_column",
"debit_credit_column",
"transaction_id_column",
"description_column",
"notes_column",
"reference_column",
"partner_name_column",
"bank_name_column",
"bank_account_column",
]
def _parse_lines(self, mapping, data_file, currency_code):
columns = dict()
try:
@@ -99,69 +157,44 @@ class AccountStatementImportSheetParser(models.TransientModel):
csv_options["delimiter"] = csv_delimiter
if mapping.quotechar:
csv_options["quotechar"] = mapping.quotechar
csv_or_xlsx = reader(
StringIO(data_file.decode(mapping.file_encoding or "utf-8")),
**csv_options
try:
decoded_file = data_file.decode(mapping.file_encoding or "utf-8")
except UnicodeDecodeError:
# Try auto guessing the format
detected_encoding = chardet.detect(data_file).get("encoding", False)
if not detected_encoding:
raise UserError(
_("No valid encoding was found for the attached file")
) from None
decoded_file = data_file.decode(detected_encoding)
csv_or_xlsx = reader(StringIO(decoded_file), **csv_options)
header = False
if not mapping.no_header:
if isinstance(csv_or_xlsx, tuple):
header = [str(value) for value in csv_or_xlsx[1].row_values(0)]
else:
header = [value.strip() for value in next(csv_or_xlsx)]
for column_name in self._get_column_names():
columns[column_name] = self._get_column_indexes(
header, column_name, mapping
)
if isinstance(csv_or_xlsx, tuple):
header = [str(value) for value in csv_or_xlsx[1].row_values(0)]
else:
header = [value.strip() for value in next(csv_or_xlsx)]
columns["timestamp_column"] = header.index(mapping.timestamp_column)
columns["currency_column"] = (
header.index(mapping.currency_column) if mapping.currency_column else None
)
columns["amount_column"] = header.index(mapping.amount_column)
columns["balance_column"] = (
header.index(mapping.balance_column) if mapping.balance_column else None
)
columns["original_currency_column"] = (
header.index(mapping.original_currency_column)
if mapping.original_currency_column
else None
)
columns["original_amount_column"] = (
header.index(mapping.original_amount_column)
if mapping.original_amount_column
else None
)
columns["debit_credit_column"] = (
header.index(mapping.debit_credit_column)
if mapping.debit_credit_column
else None
)
columns["transaction_id_column"] = (
header.index(mapping.transaction_id_column)
if mapping.transaction_id_column
else None
)
columns["description_column"] = (
header.index(mapping.description_column)
if mapping.description_column
else None
)
columns["notes_column"] = (
header.index(mapping.notes_column) if mapping.notes_column else None
)
columns["reference_column"] = (
header.index(mapping.reference_column) if mapping.reference_column else None
)
columns["partner_name_column"] = (
header.index(mapping.partner_name_column)
if mapping.partner_name_column
else None
)
columns["bank_name_column"] = (
header.index(mapping.bank_name_column) if mapping.bank_name_column else None
)
columns["bank_account_column"] = (
header.index(mapping.bank_account_column)
if mapping.bank_account_column
else None
)
return self._parse_rows(mapping, currency_code, csv_or_xlsx, columns)
def _get_values_from_column(self, values, columns, column_name):
indexes = columns[column_name]
content_l = []
max_index = len(values) - 1
for index in indexes:
if isinstance(index, int):
if index <= max_index:
content_l.append(values[index])
else:
if index in values:
content_l.append(values[index])
if all(isinstance(content, str) for content in content_l):
return " ".join(content_l)
return content_l[0]
def _parse_rows(self, mapping, currency_code, csv_or_xlsx, columns): # noqa: C901
if isinstance(csv_or_xlsx, tuple):
rows = range(1, csv_or_xlsx[1].nrows)
@@ -183,66 +216,83 @@ class AccountStatementImportSheetParser(models.TransientModel):
else:
values = list(row)
timestamp = values[columns["timestamp_column"]]
timestamp = self._get_values_from_column(
values, columns, "timestamp_column"
)
currency = (
values[columns["currency_column"]]
if columns["currency_column"] is not None
self._get_values_from_column(values, columns, "currency_column")
if columns["currency_column"]
else currency_code
)
amount = values[columns["amount_column"]]
def _decimal(column_name):
if columns[column_name]:
return self._parse_decimal(
self._get_values_from_column(values, columns, column_name),
mapping,
)
amount = _decimal("amount_column")
if not amount:
amount = abs(_decimal("amount_debit_column") or 0)
if not amount:
amount = -abs(_decimal("amount_credit_column") or 0)
balance = (
values[columns["balance_column"]]
if columns["balance_column"] is not None
self._get_values_from_column(values, columns, "balance_column")
if columns["balance_column"]
else None
)
original_currency = (
values[columns["original_currency_column"]]
if columns["original_currency_column"] is not None
self._get_values_from_column(
values, columns, "original_currency_column"
)
if columns["original_currency_column"]
else None
)
original_amount = (
values[columns["original_amount_column"]]
if columns["original_amount_column"] is not None
self._get_values_from_column(values, columns, "original_amount_column")
if columns["original_amount_column"]
else None
)
debit_credit = (
values[columns["debit_credit_column"]]
if columns["debit_credit_column"] is not None
self._get_values_from_column(values, columns, "debit_credit_column")
if columns["debit_credit_column"]
else None
)
transaction_id = (
values[columns["transaction_id_column"]]
if columns["transaction_id_column"] is not None
self._get_values_from_column(values, columns, "transaction_id_column")
if columns["transaction_id_column"]
else None
)
description = (
values[columns["description_column"]]
if columns["description_column"] is not None
self._get_values_from_column(values, columns, "description_column")
if columns["description_column"]
else None
)
notes = (
values[columns["notes_column"]]
if columns["notes_column"] is not None
self._get_values_from_column(values, columns, "notes_column")
if columns["notes_column"]
else None
)
reference = (
values[columns["reference_column"]]
if columns["reference_column"] is not None
self._get_values_from_column(values, columns, "reference_column")
if columns["reference_column"]
else None
)
partner_name = (
values[columns["partner_name_column"]]
if columns["partner_name_column"] is not None
self._get_values_from_column(values, columns, "partner_name_column")
if columns["partner_name_column"]
else None
)
bank_name = (
values[columns["bank_name_column"]]
if columns["bank_name_column"] is not None
self._get_values_from_column(values, columns, "bank_name_column")
if columns["bank_name_column"]
else None
)
bank_account = (
values[columns["bank_account_column"]]
if columns["bank_account_column"] is not None
self._get_values_from_column(values, columns, "bank_account_column")
if columns["bank_account_column"]
else None
)
@@ -252,7 +302,6 @@ class AccountStatementImportSheetParser(models.TransientModel):
if isinstance(timestamp, str):
timestamp = datetime.strptime(timestamp, mapping.timestamp_format)
amount = self._parse_decimal(amount, mapping)
if balance:
balance = self._parse_decimal(balance, mapping)
else:
@@ -359,9 +408,11 @@ class AccountStatementImportSheetParser(models.TransientModel):
if transaction_id:
note += _("Transaction ID: %s; ") % (transaction_id,)
if note and notes:
note = "{}\n{}".format(note, note.strip())
note = "{}\n{}".format(notes, note.strip())
elif note:
note = note.strip()
elif notes:
note = notes
if note:
transaction["narration"] = note

View File

@@ -0,0 +1,5 @@
"Date","Label","Debit","Credit","Balance","Partner Name","Bank Account"
"12/15/2018","Credit 20.00","0.00","20.00","-10.00","John Doe","123456789"
"12/15/2018","Credit 13.50","0.00","-13.50","-23.50","John Doe","123456789"
"12/15/2018","Debit 33.50","-33.50","0.00","10.00","Azure Interior",""
"12/15/2018","Debit 1500","1,500.00","0.00","1,510.00","Azure Interior",""
1 Date Label Debit Credit Balance Partner Name Bank Account
2 12/15/2018 Credit 20.00 0.00 20.00 -10.00 John Doe 123456789
3 12/15/2018 Credit 13.50 0.00 -13.50 -23.50 John Doe 123456789
4 12/15/2018 Debit 33.50 -33.50 0.00 10.00 Azure Interior
5 12/15/2018 Debit 1500 1,500.00 0.00 1,510.00 Azure Interior

View File

@@ -0,0 +1 @@
"12/15/2018","Your payment","EUR","1,525.00","-1,000.00","Azure Interior","","INV0001"
1 12/15/2018 Your payment EUR 1,525.00 -1,000.00 Azure Interior INV0001

View File

@@ -31,6 +31,15 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
self.AccountStatementImportSheetMappingWizard = self.env[
"account.statement.import.sheet.mapping.wizard"
]
self.suspense_account = self.env["account.account"].create(
{
"code": "987654",
"name": "Suspense Account",
"user_type_id": self.env.ref(
"account.data_account_type_current_assets"
).id,
}
)
def _data_file(self, filename, encoding=None):
mode = "rt" if encoding else "rb"
@@ -47,6 +56,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
data = self._data_file("fixtures/sample_statement_en.csv", "utf-8")
@@ -71,6 +81,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
data = self._data_file("fixtures/empty_statement_en.csv", "utf-8")
@@ -95,6 +106,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
data = self._data_file("fixtures/sample_statement_en.xlsx")
@@ -119,6 +131,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
data = self._data_file("fixtures/empty_statement_en.xlsx")
@@ -189,6 +202,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
data = self._data_file("fixtures/original_currency.csv", "utf-8")
@@ -212,6 +226,55 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
self.assertEqual(line.foreign_currency_id, self.currency_eur)
self.assertEqual(line.amount_currency, 1000.0)
def test_original_currency_no_header(self):
no_header_statement_map = self.AccountStatementImportSheetMapping.create(
{
"name": "Sample Statement",
"float_thousands_sep": "comma",
"float_decimal_sep": "dot",
"delimiter": "comma",
"quotechar": '"',
"timestamp_format": "%m/%d/%Y",
"no_header": True,
"timestamp_column": "0",
"amount_column": "3",
"original_currency_column": "2",
"original_amount_column": "4",
"description_column": "1,7",
"partner_name_column": "5",
"bank_account_column": "6",
}
)
journal = self.AccountJournal.create(
{
"name": "Bank",
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
data = self._data_file("fixtures/original_currency_no_header.csv", "utf-8")
wizard = self.AccountStatementImport.with_context(journal_id=journal.id).create(
{
"statement_filename": "fixtures/original_currency.csv",
"statement_file": data,
"sheet_mapping_id": no_header_statement_map.id,
}
)
wizard.with_context(
account_statement_import_txt_xlsx_test=True
).import_file_button()
statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)])
self.assertEqual(len(statement), 1)
self.assertEqual(len(statement.line_ids), 1)
line = statement.line_ids
self.assertEqual(line.currency_id, self.currency_usd)
self.assertEqual(line.foreign_currency_id, self.currency_eur)
self.assertEqual(line.amount_currency, 1000.0)
self.assertEqual(line.payment_ref, "Your payment INV0001")
def test_original_currency_empty(self):
journal = self.AccountJournal.create(
{
@@ -219,6 +282,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
data = self._data_file("fixtures/original_currency_empty.csv", "utf-8")
@@ -247,6 +311,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
statement_map = self.sample_statement_map.copy(
@@ -282,6 +347,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
statement_map = self.sample_statement_map.copy(
@@ -316,6 +382,7 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
statement_map = self.sample_statement_map.copy(
@@ -345,3 +412,41 @@ class TestAccountBankStatementImportTxtXlsx(common.TransactionCase):
self.assertEqual(statement.balance_start, 10.0)
self.assertEqual(statement.balance_end_real, 1510.0)
self.assertEqual(statement.balance_end, 1510.0)
def test_debit_credit_amount(self):
journal = self.AccountJournal.create(
{
"name": "Bank",
"type": "bank",
"code": "BANK",
"currency_id": self.currency_usd.id,
"suspense_account_id": self.suspense_account.id,
}
)
statement_map = self.sample_statement_map.copy(
{
"amount_debit_column": "Debit",
"amount_credit_column": "Credit",
"balance_column": "Balance",
"amount_column": None,
"original_currency_column": None,
"original_amount_column": None,
}
)
data = self._data_file("fixtures/debit_credit_amount.csv", "utf-8")
wizard = self.AccountStatementImport.with_context(journal_id=journal.id).create(
{
"statement_filename": "fixtures/debit_credit_amount.csv",
"statement_file": data,
"sheet_mapping_id": statement_map.id,
}
)
wizard.with_context(
account_statement_import_txt_xlsx_test=True
).import_file_button()
statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)])
self.assertEqual(len(statement), 1)
self.assertEqual(len(statement.line_ids), 4)
self.assertEqual(statement.balance_start, 10.0)
self.assertEqual(statement.balance_end_real, 1510.0)
self.assertEqual(statement.balance_end, 1510.0)

View File

@@ -39,6 +39,18 @@
<group>
<field name="timestamp_format" />
</group>
<group>
<field name="no_header" />
<div
class="alert alert-warning"
role="alert"
attrs="{'invisible': [('no_header', '=', False)]}"
>
<span
class="fa fa-info-circle"
/> indicate the column number in the Columns section. The first column is 0.
</div>
</group>
<group
attrs="{'invisible': [('debit_credit_column', '=', False)]}"
>
@@ -53,20 +65,33 @@
</group>
</group>
<group string="Columns">
<field name="timestamp_column" />
<field name="currency_column" />
<field name="amount_column" />
<field name="balance_column" />
<field name="original_currency_column" />
<field name="original_amount_column" />
<field name="debit_credit_column" />
<field name="transaction_id_column" />
<field name="description_column" />
<field name="notes_column" />
<field name="reference_column" />
<field name="partner_name_column" />
<field name="bank_name_column" />
<field name="bank_account_column" />
<group colspan="4" col="2">
<div class="alert alert-info" role="alert">
<span
class="fa fa-info-circle"
/> Add the column names or column number (when the file has no header).
You can concatenate multiple columns in the file into the same field, indicating the
column names or numbers separated by comma.
</div>
</group>
<group>
<field name="timestamp_column" />
<field name="currency_column" />
<field name="amount_column" />
<field name="amount_debit_column" />
<field name="amount_credit_column" />
<field name="balance_column" />
<field name="original_currency_column" />
<field name="original_amount_column" />
<field name="debit_credit_column" />
<field name="transaction_id_column" />
<field name="description_column" />
<field name="notes_column" />
<field name="reference_column" />
<field name="partner_name_column" />
<field name="bank_name_column" />
<field name="bank_account_column" />
</group>
</group>
</sheet>
</form>

View File

@@ -42,6 +42,14 @@ class AccountStatementImportSheetMappingWizard(models.TransientModel):
amount_column = fields.Char(
help="Amount of transaction in journal's currency",
)
amount_debit_column = fields.Char(
string="Debit amount column",
help="Debit amount of transaction in journal's currency",
)
amount_credit_column = fields.Boolean(
string="Credit amount column",
help="Credit amount of transaction in journal's currency",
)
balance_column = fields.Char(
help="Balance after transaction in journal's currency",
)

View File

@@ -50,7 +50,18 @@
widget="dynamic_dropdown"
values="statement_columns"
context="{'header': header}"
attrs="{'required': [('state', '=', 'final')]}"
/>
<field
name="amount_debit_column"
widget="dynamic_dropdown"
values="statement_columns"
context="{'header': header}"
/>
<field
name="amount_credit_column"
widget="dynamic_dropdown"
values="statement_columns"
context="{'header': header}"
/>
<field
name="balance_column"

View File

@@ -1,3 +1,3 @@
# generated from manifests external_dependencies
odoo_test_helper
chardet
xlrd

1
test-requirements.txt Normal file
View File

@@ -0,0 +1 @@
odoo_test_helper