diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot
index 9f22fa78d67..b643538e177 100644
--- a/addons/account/i18n/account.pot
+++ b/addons/account/i18n/account.pot
@@ -1104,6 +1104,13 @@ msgid ""
"A second payment will be created automatically in the destination journal."
msgstr ""
+#. module: account
+#. odoo-python
+#: code:addons/account/models/account_bank_statement.py:0
+#, python-format
+msgid "A statement should only contain lines from the same journal."
+msgstr ""
+
#. module: account
#: model:ir.model.constraint,message:account.constraint_account_fiscal_position_tax_tax_src_dest_uniq
msgid "A tax fiscal position could be defined only one time on same taxes."
@@ -13726,6 +13733,22 @@ msgid ""
"but you selected some of both types."
msgstr ""
+#. module: account
+#. odoo-python
+#: code:addons/account/models/account_bank_statement.py:0
+#, python-format
+msgid "The running balance (%s) doesn't match the specified ending balance."
+msgstr ""
+
+#. module: account
+#. odoo-python
+#: code:addons/account/models/account_bank_statement.py:0
+#, python-format
+msgid ""
+"The starting balance doesn't match the ending balance of the previous "
+"statement, or an earlier statement is missing."
+msgstr ""
+
#. module: account
#: model:ir.model.fields,help:account.field_account_move_line__statement_line_id
msgid "The statement line that created this entry"
@@ -14466,6 +14489,15 @@ msgstr ""
msgid "UP"
msgstr ""
+#. module: account
+#. odoo-python
+#: code:addons/account/models/account_bank_statement.py:0
+#, python-format
+msgid ""
+"Unable to create a statement due to missing transactions. You may want to "
+"reorder the transactions before proceeding."
+msgstr ""
+
#. module: account
#. odoo-python
#: code:addons/account/models/account_journal.py:0
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 6cfcdfd4783..ab25e37e01d 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _, Command
from odoo.exceptions import UserError
-
+from odoo.tools.misc import formatLang
class AccountBankStatement(models.Model):
_name = "account.bank.statement"
@@ -9,65 +9,6 @@ class AccountBankStatement(models.Model):
_order = "first_line_index desc"
_check_company_auto = True
- @api.model
- def default_get(self, fields_list):
- # EXTENDS base
- defaults = super().default_get(fields_list)
- # create statement on a saved statement line in the tree view
- if self._context.get('st_line_id'):
- st_line = self.env['account.bank.statement.line'].browse(self._context['st_line_id'])
- defaults['balance_start'] = st_line.running_balance - (st_line.amount if st_line.state == 'posted' else 0)
- return defaults
- # create statement from a new line in the tree view, not stored in the db yet
- if self._context.get('st_line_date'):
- defaults['balance_start'] = self.env['account.bank.statement.line'].search(
- domain=[
- ('date', '<=', self._context['st_line_date']),
- ('journal_id', '=', self._context.get('st_line_journal_id')),
- ],
- order='internal_index desc',
- limit=1
- ).running_balance
- return defaults
- lines = None
- # creating statements with split button
- if self._context.get('split_line_id'):
- current_st_line = self.env['account.bank.statement.line'].browse(self.env.context.get('split_line_id'))
- line_before = self.env['account.bank.statement.line'].search(
- domain=[
- ('internal_index', '<', current_st_line.internal_index),
- ('journal_id', '=', current_st_line.journal_id.id),
- ('statement_id', '!=', current_st_line.statement_id.id),
- ('statement_id', '!=', False),
- ],
- order='internal_index desc',
- limit=1,
- )
- lines = self.env['account.bank.statement.line'].search(
- domain=[
- ('internal_index', '<=', current_st_line.internal_index),
- ('internal_index', '>', line_before.internal_index or ''),
- ('journal_id', '=', current_st_line.journal_id.id),
- ],
- order='internal_index desc',
- )
- # if it is called from the action menu, we can have both start and end balances and we filter out
- # completed statements, because it is probably due to a mistake from the user
- elif self._context.get('active_model') == 'account.bank.statement.line' and self._context.get('active_ids'):
- lines = self.env['account.bank.statement.line'].browse(self._context.get('active_ids')) \
- .filtered(lambda line: not line.statement_complete) \
- .sorted()
- if not lines:
- raise UserError(_('One or more selected lines already belong to a complete statement.'))
- if lines:
- defaults['line_ids'] = [Command.set(lines.ids)]
- defaults['balance_start'] = lines[-1:].running_balance - (
- lines[-1:].amount if lines[-1:].state == 'posted' else 0
- )
- defaults['balance_end_real'] = lines[:1].running_balance
-
- return defaults
-
name = fields.Char(
string='Reference',
copy=False,
@@ -94,7 +35,7 @@ class AccountBankStatement(models.Model):
balance_start = fields.Monetary(
string='Starting Balance',
- default=0.0,
+ compute='_compute_balance_start', store=True, readonly=False,
)
# Balance end is calculated based on the statement line amounts and real starting balance.
@@ -105,7 +46,7 @@ class AccountBankStatement(models.Model):
balance_end_real = fields.Monetary(
string='Ending Balance',
- default=0.0,
+ compute='_compute_balance_end_real', store=True, readonly=False,
)
company_id = fields.Many2one(
@@ -131,9 +72,8 @@ class AccountBankStatement(models.Model):
required=True,
)
- # A statement assumed to be complete when the sum of encoded lines is equal to the difference between initial and
- # ending balances. In other words, a statement is complete when there are enough lines to fill the value between
- # initial and final balances. When the user reaches this point, the statement is not autofilled on the new lines.
+ # A statement assumed to be complete when the sum of encoded lines is equal to the difference between start and
+ # end balances.
is_complete = fields.Boolean(
compute='_compute_is_complete', store=True,
)
@@ -149,6 +89,10 @@ class AccountBankStatement(models.Model):
search='_search_is_valid',
)
+ problem_description = fields.Text(
+ compute='_compute_problem_description',
+ )
+
attachment_ids = fields.Many2many(
comodel_name='ir.attachment',
string="Attachments",
@@ -164,12 +108,45 @@ class AccountBankStatement(models.Model):
stmt.first_line_index = sorted_lines[:1].internal_index
stmt.date = sorted_lines.filtered(lambda l: l.state == 'posted')[-1:].date
+ @api.depends('create_date')
+ def _compute_balance_start(self):
+ for stmt in self.sorted(lambda x: x.first_line_index or '0'):
+ journal_id = stmt.journal_id.id or stmt.line_ids.journal_id.id
+ previous_line_with_statement = self.env['account.bank.statement.line'].search([
+ ('internal_index', '<', stmt.first_line_index),
+ ('journal_id', '=', journal_id),
+ ('state', '=', 'posted'),
+ ('statement_id', '!=', False),
+ ], limit=1)
+ balance_start = previous_line_with_statement.statement_id.balance_end_real
+
+ lines_in_between_domain = [
+ ('internal_index', '<', stmt.first_line_index),
+ ('journal_id', '=', journal_id),
+ ('state', '=', 'posted'),
+ ]
+ if previous_line_with_statement:
+ lines_in_between_domain.append(('internal_index', '>', previous_line_with_statement.internal_index))
+ # remove lines from previous statement (when multi-editing a line already in another statement)
+ previous_st_lines = previous_line_with_statement.statement_id.line_ids
+ lines_in_common = previous_st_lines.filtered(lambda l: l.id in stmt.line_ids._origin.ids)
+ balance_start -= sum(lines_in_common.mapped('amount'))
+
+ lines_in_between = self.env['account.bank.statement.line'].search(lines_in_between_domain)
+ balance_start += sum(lines_in_between.mapped('amount'))
+
+ stmt.balance_start = balance_start
+
@api.depends('balance_start', 'line_ids.amount', 'line_ids.state')
def _compute_balance_end(self):
- for statement in self:
- statement.balance_end = statement.balance_start + sum(
- statement.line_ids.filtered(lambda l: l.state == 'posted').mapped('amount')
- )
+ for stmt in self:
+ lines = stmt.line_ids.filtered(lambda x: x.state == 'posted')
+ stmt.balance_end = stmt.balance_start + sum(lines.mapped('amount'))
+
+ @api.depends('balance_start')
+ def _compute_balance_end_real(self):
+ for stmt in self:
+ stmt.balance_end_real = stmt.balance_end
@api.depends('journal_id')
def _compute_currency_id(self):
@@ -181,19 +158,34 @@ class AccountBankStatement(models.Model):
for statement in self:
statement.journal_id = statement.line_ids.journal_id
- @api.depends('balance_end_real', 'balance_end')
+ @api.depends('balance_end', 'balance_end_real', 'line_ids.amount', 'line_ids.state')
def _compute_is_complete(self):
for stmt in self:
stmt.is_complete = stmt.line_ids.filtered(lambda l: l.state == 'posted') and stmt.currency_id.compare_amounts(
stmt.balance_end, stmt.balance_end_real) == 0
+ @api.depends('balance_end', 'balance_end_real')
def _compute_is_valid(self):
# we extract the invalid statements, the statements with no lines and the first statement are not in the query
# because they don't have a previous statement, so they are excluded from the join, and we consider them valid.
# if we have extracted the valid ones, we would have to mark above-mentioned statements valid manually
- invalids = self.filtered(lambda s: s.id in self._get_invalid_statement_ids())
- invalids.is_valid = False
- (self - invalids).is_valid = True
+ # For new statements, a sql query can't be used
+ if len(self) == 1:
+ self.is_valid = self._get_statement_validity()
+ else:
+ invalids = self.filtered(lambda s: s.id in self._get_invalid_statement_ids())
+ invalids.is_valid = False
+ (self - invalids).is_valid = True
+
+ @api.depends('is_valid', 'is_complete')
+ def _compute_problem_description(self):
+ for stmt in self:
+ description = None
+ if not stmt.is_valid:
+ description = _("The starting balance doesn't match the ending balance of the previous statement, or an earlier statement is missing.")
+ elif not stmt.is_complete:
+ description = _("The running balance (%s) doesn't match the specified ending balance.", formatLang(self.env, stmt.balance_end, currency_obj=stmt.currency_id))
+ stmt.problem_description = description
def _search_is_valid(self, operator, value):
if operator not in ('=', '!=', '<>'):
@@ -203,28 +195,22 @@ class AccountBankStatement(models.Model):
return [('id', 'in', invalid_ids)]
return [('id', 'not in', invalid_ids)]
- # -------------------------------------------------------------------------
- # CRUD
- # -------------------------------------------------------------------------
- @api.model_create_multi
- def create(self, vals_list):
- # EXTENDS base
- # If we are doing a split, we have to correct the split statement's balance to keep both original and new
- # statements valid.
- if self._context.get('split_line_id'):
- old_statement = self.env['account.bank.statement.line'].browse(self._context.get('split_line_id')).statement_id
- old_lines = old_statement.line_ids
- statements = super().create(vals_list)
- if self._context.get('split_line_id'):
- statements.ensure_one()
- if old_statement:
- net_change = sum((statements.line_ids & old_lines).filtered(lambda l: l.state == 'posted').mapped('amount'))
- old_statement.balance_start += net_change
- return statements
-
# -------------------------------------------------------------------------
# BUSINESS METHODS
# -------------------------------------------------------------------------
+ def _get_statement_validity(self):
+ """ Compares the balance_start to the previous statements balance_end_real """
+ self.ensure_one()
+ previous = self.env['account.bank.statement'].search(
+ [
+ ('first_line_index', '<', self.first_line_index),
+ ('journal_id', '=', self.journal_id.id),
+ ],
+ limit=1,
+ order='first_line_index DESC',
+ )
+ return not previous or self.currency_id.compare_amounts(self.balance_start, previous.balance_end_real) == 0
+
def _get_invalid_statement_ids(self, all_statements=None):
""" Returns the statements that are invalid for _compute and _search methods."""
@@ -252,3 +238,63 @@ class AccountBankStatement(models.Model):
})
res = self.env.cr.fetchall()
return [r[0] for r in res]
+
+ # -------------------------------------------------------------------------
+ # LOW-LEVEL METHODS
+ # -------------------------------------------------------------------------
+
+ @api.model
+ def default_get(self, fields_list):
+ # EXTENDS base
+ defaults = super().default_get(fields_list)
+
+ if 'line_ids' not in fields_list:
+ return defaults
+
+ active_ids = self._context.get('active_ids')
+ context_split_line_id = self._context.get('split_line_id')
+ context_st_line_id = self._context.get('st_line_id')
+ lines = None
+ # creating statements with split button
+ if context_split_line_id:
+ current_st_line = self.env['account.bank.statement.line'].browse(context_split_line_id)
+ line_before = self.env['account.bank.statement.line'].search(
+ domain=[
+ ('internal_index', '<', current_st_line.internal_index),
+ ('journal_id', '=', current_st_line.journal_id.id),
+ ('statement_id', '!=', current_st_line.statement_id.id),
+ ('statement_id', '!=', False),
+ ],
+ order='internal_index desc',
+ limit=1,
+ )
+ lines = self.env['account.bank.statement.line'].search(
+ domain=[
+ ('internal_index', '<=', current_st_line.internal_index),
+ ('internal_index', '>', line_before.internal_index or ''),
+ ('journal_id', '=', current_st_line.journal_id.id),
+ ],
+ order='internal_index desc',
+ )
+ # single line edit
+ elif context_st_line_id and len(active_ids) <= 1:
+ lines = self.env['account.bank.statement.line'].browse(context_st_line_id)
+ # multi edit
+ elif context_st_line_id and len(active_ids) > 1:
+ lines = self.env['account.bank.statement.line'].browse(active_ids).sorted()
+ if len(lines.journal_id) > 1:
+ raise UserError(_("A statement should only contain lines from the same journal."))
+ # Check that the selected lines are contiguous
+ indexes = lines.mapped('internal_index')
+ count_lines_between = self.env['account.bank.statement.line'].search_count([
+ ('internal_index', '>=', min(indexes)),
+ ('internal_index', '<=', max(indexes)),
+ ('journal_id', '=', lines.journal_id.id),
+ ])
+ if len(lines) != count_lines_between:
+ raise UserError(_("Unable to create a statement due to missing transactions. You may want to reorder the transactions before proceeding."))
+
+ if lines:
+ defaults['line_ids'] = [Command.set(lines.ids)]
+
+ return defaults
diff --git a/addons/account/models/account_bank_statement_line.py b/addons/account/models/account_bank_statement_line.py
index aebeee8641d..56a998234d3 100644
--- a/addons/account/models/account_bank_statement_line.py
+++ b/addons/account/models/account_bank_statement_line.py
@@ -27,16 +27,15 @@ class AccountBankStatementLine(models.Model):
# to enter the next transaction, they do not have to enter the date and the statement every time until the
# statement is completed. It is only possible if we know the journal that is used, so it can only be done
# in a view in which the journal is already set and so is single journal view.
- if'journal_id' in defaults:
+ if 'journal_id' in defaults and 'date' in fields_list:
last_line = self.search([
('journal_id', '=', defaults.get('journal_id')),
('state', '=', 'posted'),
], limit=1)
statement = last_line.statement_id
- if statement and not statement.is_complete:
- defaults.setdefault('statement_id', statement.id)
+ if statement:
defaults.setdefault('date', statement.date)
- elif last_line and not statement:
+ elif last_line:
defaults.setdefault('date', last_line.date)
return defaults
@@ -276,15 +275,6 @@ class AccountBankStatementLine(models.Model):
# The journal entry seems reconciled.
st_line.is_reconciled = True
- @api.onchange('journal_id')
- def _onchange_journal_id(self):
- """
- Reset the statement line when the journal is changed. In some rare cases that journal is not in the context
- the journal_id field might be accessible to the user. In this cse we need to reset the statement_id field when
- the journal_id is changed.
- :return:
- """
- self.statement_id = self._get_default_statement(self.journal_id.id, self.date)
# -------------------------------------------------------------------------
# CONSTRAINT METHODS
diff --git a/addons/account/tests/test_account_bank_statement.py b/addons/account/tests/test_account_bank_statement.py
index 157e921e12d..6546de84c8e 100644
--- a/addons/account/tests/test_account_bank_statement.py
+++ b/addons/account/tests/test_account_bank_statement.py
@@ -548,24 +548,25 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
# create a valid and complete statement as the first lines (no statement before)
line1 = self.create_bank_transaction(1, '2020-01-10')
line2 = self.create_bank_transaction(2, '2020-01-11')
- statement1 = self.env['account.bank.statement'].create({
- 'line_ids': [Command.set((line1 + line2).ids)],
- 'balance_start': 0,
- 'balance_end_real': 3,
- })
+ statement1 = self.env['account.bank.statement'].with_context(st_line_id=line1.id, active_ids=[line1.id, line2.id]).create({})
self.assertRecordValues(statement1, [{
'is_complete': True,
'is_valid': True,
+ 'balance_start': 0,
+ 'balance_end': 3,
+ 'balance_end_real': 3,
}])
# remove the first line, so not complete but it is still valid because there is no statement before
line1.statement_id = False
- statement1.invalidate_recordset(['is_valid'])
self.assertRecordValues(statement1, [{
'is_complete': False,
'is_valid': True,
+ 'balance_start': 0,
+ 'balance_end': 2,
+ 'balance_end_real': 3,
}])
# create a new line in the statement to make it complete again. Starting value does not match the last line
- # but it is still valid because the previous line has no statement
+ # but it is still valid because it is the first statement
line3 = self.create_bank_transaction(1, '2020-01-12', statement=statement1)
statement1.invalidate_recordset(['is_valid'])
self.assertRecordValues(statement1, [{
@@ -586,12 +587,11 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
'is_complete': True,
'is_valid': True, # first statement
}])
- # Fix the statement balance start, so the statement is not complete (end - start = 2, vs sum lines = 3)
- # but it is matched with the previous statements balance end
+ # Fix the statement balance start, the balance_end_real is computed, making it complete
statement1.balance_start = 1
statement1.invalidate_recordset(['is_valid'])
self.assertRecordValues(statement1, [{
- 'is_complete': False,
+ 'is_complete': True,
'is_valid': True,
}])
# change the prev statement so the end balance does not match the start balance of statement 1
@@ -599,11 +599,11 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
statement2.flush_recordset(['balance_end_real'])
statement1.invalidate_recordset(['is_valid'])
self.assertRecordValues(statement1, [{
- 'is_complete': False,
+ 'is_complete': True,
'is_valid': False,
}])
- # make the statement valid again
- statement1.balance_start = 10
+ # make the statement valid again, but keep it incomplete
+ statement1.write({'balance_start': 10, 'balance_end_real': 3})
statement1.invalidate_recordset(['is_valid'])
self.assertRecordValues(statement1, [{
'is_complete': False,
@@ -629,7 +629,7 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
{'date': fields.Date.from_string('2020-01-13'), 'statement_id': statement1.id},
])
- # changing statement 2 balance makes statement 1 valid,
+ # changing statement 2 balance makes statement 1 invalid,
# but making statement 1 the first statement should make it valid again
statement2.balance_end_real = 100
statement2.flush_recordset(['balance_end_real'])
@@ -684,7 +684,7 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
{'is_valid': True, 'balance_start': 10, 'balance_end_real': -5,
'date': fields.Date.from_string('2020-01-11')},
{'is_valid': True, 'balance_start': False, 'balance_end_real': 100, 'date': False},
- {'is_valid': True, 'balance_start': -5, 'balance_end_real': False,
+ {'is_valid': True, 'balance_start': -5, 'balance_end_real': -15,
'date': fields.Date.from_string('2020-01-13')},
])
@@ -748,7 +748,8 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
# Put line2 -> line4 inside a statement with a wrong balance_end_real.
(line2 + line3 + line4).statement_id = statement1 = \
- self.env['account.bank.statement'].create({'balance_end_real': 9})
+ self.env['account.bank.statement'].create({'balance_start': 20, 'balance_end_real': 29})
+
self.assertRecordValues(statement1, [{
'is_complete': True,
}])
@@ -756,14 +757,10 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
statement1.invalidate_recordset(['is_valid'])
statement1.balance_start = 26
self.assertRecordValues(statement1, [{
- 'is_complete': False,
+ 'is_complete': True,
+ 'balance_end_real': 35, # autocorrect
}])
- statement1.invalidate_recordset(['is_valid'])
- statement1.balance_end_real = 35
- self.assertRecordValues(statement1, [{
- 'is_complete': True,
- }])
# line3, line4 and line5 have the same date. Move line5 at the first place using the sequence.
line5.sequence = -1
statement1.invalidate_recordset(['is_valid'])
@@ -775,7 +772,8 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
statement1.balance_start = 21
statement1.invalidate_recordset(['is_valid'])
self.assertRecordValues(statement1, [{
- 'is_complete': False,
+ 'is_complete': True,
+ 'balance_end_real': 30,
}])
self.env['account.bank.statement.line'].invalidate_model(fnames=['running_balance'])
@@ -896,28 +894,26 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
# Split the last 2 lines by splitting on the line before last.
statement1 = self.env['account.bank.statement'].with_context({'split_line_id': line7.id}).create({})
self.assertRecordValues(statement1, [{
+ 'balance_start': 0.0,
+ 'balance_end': 15.0,
+ 'balance_end_real': 15.0,
'is_complete': True,
- 'balance_end_real': 15,
- 'balance_end': 15,
- 'balance_start': 0,
+ 'is_valid': True,
}])
- self.assertRecordValues(line7 + line8, [
+ self.assertRecordValues(line7 + line8 + line6, [
{'amount': 7, 'statement_id': statement1.id},
{'amount': 8, 'statement_id': statement1.id},
+ {'amount': 6, 'statement_id': False},
])
# Split on a line adjutant to another statement
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
statement2 = self.env['account.bank.statement'].with_context({'split_line_id': line6.id}).create({})
- self.assertRecordValues(statement1, [{
- 'is_complete': True,
- 'balance_end_real': 15,
- 'balance_start': 0,
- }])
self.assertRecordValues(statement2, [{
+ 'balance_start': 15.0,
+ 'balance_end': 21.0,
+ 'balance_end_real': 21.0,
'is_complete': True,
- 'balance_end_real': 21,
- 'balance_start': 15,
+ 'is_valid': True,
}])
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
@@ -936,8 +932,14 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
# Split on a line with a gap to another statement
statement1.unlink()
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
statement3 = self.env['account.bank.statement'].with_context({'split_line_id': line3.id}).create({})
+ self.assertRecordValues(statement3, [{
+ 'balance_start': 21.0,
+ 'balance_end': 33.0,
+ 'balance_end_real': 33.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ }])
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -954,7 +956,22 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
)
# Split on a line with a single line statement
statement4 = self.env['account.bank.statement'].with_context({'split_line_id': line6.id}).create({})
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
+ self.assertRecordValues(statement3 + statement4, [
+ {
+ 'balance_start': 21.0,
+ 'balance_end': 33.0,
+ 'balance_end_real': 33.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ },
+ {
+ 'balance_start': 0.0,
+ 'balance_end': 21.0,
+ 'balance_end_real': 21.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ },
+ ])
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -970,8 +987,14 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
],
)
# check double split on a single line
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
statement5 = self.env['account.bank.statement'].with_context({'split_line_id': line2.id}).create({})
+ self.assertRecordValues(statement5, [{
+ 'balance_start': 33.0,
+ 'balance_end': 35.0,
+ 'balance_end_real': 35.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ }])
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -986,8 +1009,14 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
{'amount': 8, 'running_balance': 8, 'statement_id': statement4.id},
],
)
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
- statement6 = self.env['account.bank.statement'].with_context({'split_line_id': line2.id}).create({})
+ statement6 = self.env['account.bank.statement'].with_context({'split_line_id': line2.id}).create({'reference': '6'})
+ self.assertRecordValues(statement6, [{
+ 'balance_start': 33.0,
+ 'balance_end': 35.0,
+ 'balance_end_real': 35.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ }])
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -1004,9 +1033,34 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
)
# Split in the middle of a statement
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
statement7 = self.env['account.bank.statement'].with_context({'split_line_id': line4.id}).create({})
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
+ self.assertRecordValues(statement3 + statement7, [
+ {
+ 'balance_start': 21.0,
+ 'balance_end': 24.0,
+ 'balance_end_real': 33.0,
+ 'is_complete': False,
+ 'is_valid': False,
+ },
+ {
+ 'balance_start': 21.0,
+ 'balance_end': 30.0,
+ 'balance_end_real': 30.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ },
+ ])
+
+ # Fix statement3
+ statement3._compute_balance_start()
+ self.assertRecordValues(statement3, [{
+ 'balance_start': 30.0,
+ 'balance_end': 33.0,
+ 'balance_end_real': 33.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ }])
+
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -1021,14 +1075,25 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
{'amount': 8, 'running_balance': 8, 'statement_id': statement4.id},
],
)
- self.assertRecordValues(statement3 + statement7, [
- {'balance_end_real': 33, 'balance_start': 30, 'is_complete': True, 'is_valid': True, },
- {'balance_end_real': 30, 'balance_start': 21, 'is_complete': True, 'is_valid': True, },
- ])
# split at start of another statement
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
statement8 = self.env['account.bank.statement'].with_context({'split_line_id': line6.id}).create({})
+ self.assertRecordValues(statement7 + statement8, [
+ {
+ 'balance_end_real': 30.0,
+ 'balance_end': 30.0,
+ 'balance_start': 21.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ },
+ {
+ 'balance_end_real': 21.0,
+ 'balance_end': 21.0,
+ 'balance_start': 0.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ },
+ ])
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -1045,8 +1110,34 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
)
# split at end of another statement
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
statement9 = self.env['account.bank.statement'].with_context({'split_line_id': line8.id}).create({})
+ self.assertRecordValues(statement8 + statement9, [
+ {
+ 'balance_end_real': 21.0,
+ 'balance_end': 13.0,
+ 'balance_start': 0.0,
+ 'is_complete': False,
+ 'is_valid': False,
+ },
+ {
+ 'balance_end_real': 8.0,
+ 'balance_end': 8.0,
+ 'balance_start': 0.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ },
+ ])
+
+ # Fix statement8
+ statement8._compute_balance_start() # TODO: add_to_compute not working, why?
+ self.assertRecordValues(statement8, [{
+ 'balance_end_real': 21.0,
+ 'balance_end': 21.0,
+ 'balance_start': 8.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ }])
+
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -1061,14 +1152,16 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
{'amount': 8, 'statement_id': statement9.id},
],
)
- self.assertRecordValues(statement8 + statement9, [
- {'balance_end_real': 21, 'balance_start': 8, 'is_complete': True, 'is_valid': True, },
- {'balance_end_real': 8, 'balance_start': 0, 'is_complete': True, 'is_valid': True, },
- ])
# split at most recent line
- self.env['account.bank.statement.line'].invalidate_model(['running_balance'])
statement10 = self.env['account.bank.statement'].with_context({'split_line_id': line1.id}).create({})
+ self.assertRecordValues(statement10, [{
+ 'balance_start': 35.0,
+ 'balance_end': 36.0,
+ 'balance_end_real': 36.0,
+ 'is_complete': True,
+ 'is_valid': True,
+ }])
self.assertRecordValues(
self.env['account.bank.statement.line'].search([('company_id', '=', self.env.company.id)]),
[
@@ -1089,21 +1182,20 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
('company_id', '=', self.env.company.id),
])
all_statements.invalidate_recordset(['is_valid'])
- self.assertEqual(len(all_statements), 6)
- self.assertEqual(all_statements.mapped('is_valid'), [True] * len(all_statements))
- self.assertEqual(all_statements.mapped('is_complete'), [True] * len(all_statements))
+ self.assertRecordValues(all_statements, [{'is_valid': True, 'is_complete': True}] * len(all_statements))
def test_statement_with_canceled_lines(self):
- line1 = self.create_bank_transaction(1, '2020-01-10')
- line2 = self.create_bank_transaction(2, '2020-01-11')
+ line1 = self.create_bank_transaction(1, '2020-01-10', journal=self.bank_journal_2)
+ line2 = self.create_bank_transaction(2, '2020-01-11', journal=self.bank_journal_2)
statement1 = self.env['account.bank.statement'].create({
'line_ids': [Command.set((line1 + line2).ids)],
- 'balance_start': 0,
- 'balance_end_real': 3,
})
self.assertRecordValues(statement1, [{
'is_complete': True,
+ 'is_valid': True,
'date': fields.Date.from_string(line2.date),
+ 'balance_start': 0,
+ 'balance_end_real': 3,
}])
# test canceling a line
line2.move_id.button_cancel()
@@ -1112,15 +1204,15 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
'balance_end': 1,
'date': fields.Date.from_string(line1.date),
}])
- # test add a line with same amount as a canceled line makes statement complete again
- line3 = self.create_bank_transaction(2, '2020-01-12', statement=statement1)
+ # add a line with same amount as the canceled line makes statement1 complete again
+ line3 = self.create_bank_transaction(2, '2020-01-12', journal=self.bank_journal_2, statement=statement1)
self.assertRecordValues(statement1, [{
'is_complete': True,
'balance_end': 3,
'date': fields.Date.from_string(line3.date),
}])
# test adding a draft line to a statement, nothing should be changed in statement
- line4 = self.create_bank_transaction(4, '2020-01-13')
+ line4 = self.create_bank_transaction(4, '2020-01-13', journal=self.bank_journal_2)
line4.move_id.button_cancel()
line4.move_id.button_draft()
statement1.line_ids |= line4
@@ -1132,9 +1224,9 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
# test split with canceled/draft lines
statement2 = self.env['account.bank.statement'].with_context({'split_line_id': line2.id}).create({})
self.assertRecordValues(statement1 + statement2, [{
- 'is_complete': True,
- 'balance_end': 3,
- 'balance_start': 1,
+ 'is_complete': False,
+ 'balance_end': 2,
+ 'balance_start': 0,
'line_ids': [line4.id, line3.id],
}, {
'is_complete': True,
@@ -1148,21 +1240,22 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
line3.move_id.button_cancel()
self.assertRecordValues(statement1, [{
'is_complete': False,
- 'balance_start': 1,
- 'balance_end': 1,
+ 'balance_start': 0,
+ 'balance_end': 0,
}])
+
# create a statement line with already canceled lines
statement3 = self.env['account.bank.statement'].create({
'line_ids': [Command.set((line3 + line4).ids)],
})
self.assertRecordValues(statement1 + statement3, [{
'is_complete': False,
- 'balance_start': 1,
- 'balance_end': 1,
- }, {
- 'is_complete': False,
- 'balance_start': 0, # no value is given for balance start and it is not a split
+ 'balance_start': 0,
'balance_end': 0,
+ }, {
+ 'is_complete': False, # no posted transactions
+ 'balance_start': 1, # from statement2's balance_end_real
+ 'balance_end': 1, # no posted transactions
}])
def test_create_statement_line_with_inconsistent_currencies(self):
@@ -1181,3 +1274,114 @@ class TestAccountBankStatementLine(AccountTestInvoicingCommon):
'amount': 200.0,
'amount_currency': 0.0,
}])
+
+ def test_statement_balance_warnings(self):
+ ''' Ensure that new statements have the correct opening/closing balances or warnings '''
+ lines = [
+ self.create_bank_transaction(amount, date, journal=self.bank_journal_2)
+ for amount, date in [
+ (10.0, '2019-01-01'),
+ (15.0, '2019-01-02'),
+ (20.0, '2019-01-03'),
+ (30.0, '2019-01-03'),
+ (40.0, '2019-01-04'),
+ (50.0, '2019-01-05'),
+ ]
+ ]
+
+ # new statement from single line
+ contexts = [{
+ 'active_ids': [line.id],
+ 'st_line_id': line.id,
+ } for line in lines[:3]]
+
+ self.assertRecordValues(self.env['account.bank.statement'].with_context(contexts[1]).new({}), [{
+ 'balance_start': 10.0,
+ 'balance_end_real': 25.0,
+ 'is_valid': True,
+ 'is_complete': True,
+ }])
+
+ st1 = self.env['account.bank.statement'].with_context(contexts[0]).create({'name': 'Statement 1'})
+ self.assertRecordValues(st1, [{
+ 'balance_start': 0.0,
+ 'balance_end_real': 10.0,
+ 'is_valid': True,
+ 'is_complete': True,
+ }])
+ self.assertEqual(lines[0].statement_id, st1)
+
+ self.assertRecordValues(self.env['account.bank.statement'].with_context(contexts[2]).new(), [{
+ 'balance_start': 25.0,
+ 'balance_end_real': 45.0,
+ 'is_valid': False,
+ 'is_complete': True,
+ }])
+
+ self.assertRecordValues(self.env['account.bank.statement'].with_context(contexts[1]).new(), [{
+ 'balance_start': 10.0,
+ 'balance_end_real': 25.0,
+ 'is_valid': True,
+ 'is_complete': True,
+ }])
+
+ # multi line edit, one line with statement
+ context = {
+ 'active_ids': [line.id for line in lines[:3]],
+ 'st_line_id': lines[2].id,
+ }
+ self.assertRecordValues(self.env['account.bank.statement'].with_context(context).new({}), [{
+ 'balance_start': 0.0,
+ 'balance_end_real': 45.0,
+ 'is_valid': True,
+ 'is_complete': True,
+ 'line_ids': [lines[0].id, lines[1].id, lines[2].id],
+ }])
+
+ # multi line edit, skip lines
+ context = {
+ 'active_ids': [line.id for line in lines[2:4]],
+ 'st_line_id': lines[3].id,
+ }
+ self.assertRecordValues(self.env['account.bank.statement'].with_context(context).new({}), [{
+ 'balance_start': 25.0,
+ 'balance_end_real': 75.0,
+ 'is_valid': False,
+ 'is_complete': True,
+ 'line_ids': [lines[2].id, lines[3].id],
+ }])
+
+ # multi line edit
+ expected_st_vals = [{
+ 'balance_start': 10.0,
+ 'balance_end_real': 75.0,
+ 'is_valid': True,
+ 'is_complete': True,
+ 'line_ids': [lines[1].id, lines[2].id, lines[3].id],
+ }]
+ context = {
+ 'active_ids': [line.id for line in lines[1:4]],
+ 'st_line_id': lines[3].id,
+ }
+ self.assertRecordValues(self.env['account.bank.statement'].with_context(context).new({}), expected_st_vals)
+
+ # split button
+ self.assertRecordValues(self.env['account.bank.statement'].with_context({'split_line_id': lines[3].id}).new({}),
+ expected_st_vals)
+
+ # raise error if lines skipped during multi-edit
+ context = {
+ 'active_ids': [lines[1].id, lines[3].id],
+ 'st_line_id': lines[3].id,
+ }
+ with self.assertRaises(UserError):
+ self.env['account.bank.statement'].with_context(context).create({})
+
+ # create the second statement using split button
+ st2 = self.env['account.bank.statement'].with_context({'split_line_id': lines[-1].id}).create({'name': 'Statement 2'})
+ self.assertRecordValues(st2, [{
+ 'balance_start': 10.0,
+ 'balance_end_real': 165.0,
+ 'is_valid': True,
+ 'is_complete': True,
+ }])
diff --git a/addons/account/views/account_bank_statement_views.xml b/addons/account/views/account_bank_statement_views.xml
index 23682eb9bc5..42d0e7ad0b8 100644
--- a/addons/account/views/account_bank_statement_views.xml
+++ b/addons/account/views/account_bank_statement_views.xml
@@ -7,7 +7,8 @@
account.bank.statement.tree
account.bank.statement
-
@@ -31,7 +32,7 @@
-
+