diff --git a/addons/account/tests/test_transfer_wizard.py b/addons/account/tests/test_transfer_wizard.py index caac812a0cc..610c672f17e 100644 --- a/addons/account/tests/test_transfer_wizard.py +++ b/addons/account/tests/test_transfer_wizard.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- +from odoo import fields, Command from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.tests import tagged, Form import time + @tagged('post_install', '-at_install') class TestTransferWizard(AccountTestInvoicingCommon): @@ -285,3 +287,58 @@ class TestTransferWizard(AccountTestInvoicingCommon): self.assertEqual(len(destination_lines), 2, "Two lines should have been created on destination account: one for each currency (the lines with same partner and currency should have been aggregated)") self.assertAlmostEqual(destination_lines.filtered(lambda x: x.currency_id == self.test_currency_1).amount_currency, -10, self.test_currency_1.decimal_places) self.assertAlmostEqual(destination_lines.filtered(lambda x: x.currency_id == self.test_currency_2).amount_currency, -756, self.test_currency_2.decimal_places) + + def test_period_change_lock_date(self): + """ Test that the period change wizard correctly handles the lock date: if the original entry is dated + before the lock date, the adjustment entry is created on the first end of month after the lock date. + """ + # Set up accrual accounts + self.company_data['company'].expense_accrual_account_id = self.env['account.account'].create({ + 'name': 'Expense Accrual Account', + 'code': '113226', + 'account_type': 'asset_prepayments', + 'reconcile': True, + }) + self.company_data['company'].revenue_accrual_account_id = self.env['account.account'].create({ + 'name': 'Revenue Accrual Account', + 'code': '226113', + 'account_type': 'liability_current', + 'reconcile': True, + }) + + # Create a move before the lock date + move = self.env['account.move'].create({ + 'journal_id': self.company_data['default_journal_sale'].id, + 'date': '2019-01-01', + 'line_ids': [ + Command.create({'account_id': self.accounts[0].id, 'debit': 1000, }), + Command.create({'account_id': self.accounts[0].id, 'credit': 1000, }), + ] + }) + move.action_post() + + # Set the lock date + move.company_id.write({'period_lock_date': '2019-02-28', 'fiscalyear_lock_date': '2019-02-28'}) + + # Open the transfer wizard at a date after the lock date + wizard = self.env['account.automatic.entry.wizard'] \ + .with_context(active_model='account.move.line', active_ids=move.line_ids[0].ids) \ + .create({ + 'action': 'change_period', + 'date': '2019-05-01', + 'journal_id': self.company_data['default_journal_misc'].id, + }) + + # Check that the 'The date is being set prior to the user lock date' message appears. + self.assertRecordValues(wizard, [{ + 'lock_date_message': 'The date is being set prior to the user lock date 02/28/2019. ' + 'The Journal Entry will be accounted on 03/31/2019 upon posting.' + }]) + + # Create the adjustment move. + wizard_res = wizard.do_action() + + # Check that the adjustment move was created on the first end of month after the lock date. + created_moves = self.env['account.move'].browse(wizard_res['domain'][0][2]) + adjustment_move = created_moves[1] # There are 2 created moves; the adjustment move is the second one. + self.assertRecordValues(adjustment_move, [{'date': fields.Date.to_date('2019-03-31')}]) diff --git a/addons/account/wizard/account_automatic_entry_wizard.py b/addons/account/wizard/account_automatic_entry_wizard.py index d4bc48a0fc4..bdb024e95c3 100644 --- a/addons/account/wizard/account_automatic_entry_wizard.py +++ b/addons/account/wizard/account_automatic_entry_wizard.py @@ -3,9 +3,9 @@ from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError from odoo.tools.misc import format_date, formatLang from odoo.tools.float_utils import float_repr +from odoo.tools import groupby from collections import defaultdict -from itertools import groupby from markupsafe import Markup, escape import json @@ -43,6 +43,7 @@ class AutomaticEntryWizard(models.TransientModel): compute="_compute_revenue_accrual_account", inverse="_inverse_revenue_accrual_account", ) + lock_date_message = fields.Char(string="Lock Date Message", compute="_compute_lock_date_message") # change account destination_account_id = fields.Many2one(string="To", comodel_name='account.account', help="Account to transfer to.") @@ -101,6 +102,17 @@ class AutomaticEntryWizard(models.TransientModel): for record in self: record.account_type = 'income' if sum(record.move_line_ids.mapped('balance')) < 0 else 'expense' + @api.depends('action', 'move_line_ids') + def _compute_lock_date_message(self): + for record in self: + record.lock_date_message = False + if record.action == 'change_period': + for aml in record.move_line_ids: + lock_date_message = aml.move_id._get_lock_date_message(aml.date, aml.move_id._affect_tax_report()) + if lock_date_message: + record.lock_date_message = lock_date_message + break + @api.depends('destination_account_id') def _compute_display_currency_helper(self): for record in self: @@ -112,11 +124,6 @@ class AutomaticEntryWizard(models.TransientModel): if wizard.move_line_ids.move_id._get_violated_lock_dates(wizard.date, False): raise ValidationError(_("The date selected is protected by a lock date")) - if wizard.action == 'change_period': - for move in wizard.move_line_ids.move_id: - if move._get_violated_lock_dates(move.date, False): - raise ValidationError(_("The date of some related entries is protected by a lock date")) - @api.model def default_get(self, fields): res = super().default_get(fields) @@ -208,6 +215,12 @@ class AutomaticEntryWizard(models.TransientModel): }] def _get_move_dict_vals_change_period(self): + reference_move = self.env['account.move'].new({'journal_id': self.journal_id.id}) + + def get_lock_safe_date(aml): + # Use a reference move in the correct journal because _get_accounting_date depends on the journal sequence. + return reference_move._get_accounting_date(aml.date, aml.move_id._affect_tax_report()) + # set the change_period account on the selected journal items accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account @@ -220,7 +233,7 @@ class AutomaticEntryWizard(models.TransientModel): 'journal_id': self.journal_id.id, }} # complete the account.move data - for date, grouped_lines in groupby(self.move_line_ids, lambda m: m.move_id.date): + for date, grouped_lines in groupby(self.move_line_ids, get_lock_safe_date): grouped_lines = list(grouped_lines) amount = sum(l.balance for l in grouped_lines) move_data[date] = { @@ -261,7 +274,7 @@ class AutomaticEntryWizard(models.TransientModel): 'analytic_distribution': aml.analytic_distribution, }), ] - move_data[aml.move_id.date]['line_ids'] += [ + move_data[get_lock_safe_date(aml)]['line_ids'] += [ (0, 0, { 'name': aml.name or '', 'debit': reported_credit, @@ -344,7 +357,7 @@ class AutomaticEntryWizard(models.TransientModel): accrual_move_offsets = defaultdict(int) for move in self.move_line_ids.move_id: amount = sum((self.move_line_ids._origin & move.line_ids).mapped('balance')) - accrual_move = created_moves[1:].filtered(lambda m: m.date == move.date) + accrual_move = created_moves[1:].filtered(lambda m: m.date == m._get_accounting_date(move.date, move._affect_tax_report())) if accrual_account.reconcile and accrual_move.state == 'posted' and destination_move.state == 'posted': destination_move_lines = destination_move.mapped('line_ids').filtered(lambda line: line.account_id == accrual_account)[destination_move_offset:destination_move_offset+2] diff --git a/addons/account/wizard/account_automatic_entry_wizard_views.xml b/addons/account/wizard/account_automatic_entry_wizard_views.xml index 6911275cef0..b2fceded0e1 100644 --- a/addons/account/wizard/account_automatic_entry_wizard_views.xml +++ b/addons/account/wizard/account_automatic_entry_wizard_views.xml @@ -15,6 +15,9 @@ The selected destination account is set to use a specific currency. Every entry transferred to it will be converted into this currency, causing the loss of any pre-existing foreign currency amount. +
+ +