diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 61ec516cf0b..fde3db36f04 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -62,13 +62,13 @@ class AccountMove(models.Model): move.matched_percentage = 1.0 else: move.matched_percentage = total_reconciled / total_amount - + @api.multi @api.depends('line_ids.reconcile_model_id') def _compute_reconcile_model(self): for move in self: move.reconcile_model_id = move.line_ids.mapped('reconcile_model_id') - + @api.model @api.depends('reconcile_model_id') def _search_reconcile_model(self, operator, operand): @@ -688,6 +688,22 @@ class AccountMoveLine(models.Model): rec.update({'credit': balance}) return rec + @api.multi + @api.constrains('tax_ids', 'tax_line_id') + def _check_tax_lock_date1(self): + for line in self: + if line.date <= (line.company_id.tax_lock_date or date.min): + raise ValidationError(_("The operation is refused as it would impact an already issued tax statement. " + + "Please change the journal entry date or the tax lock date set in the settings ({}) to proceed").format(line.company_id.tax_lock_date or date.min)) + + @api.multi + @api.constrains('credit', 'debit', 'date') + def _check_tax_lock_date2(self): + for line in self: + if (line.tax_ids or line.tax_line_id) and line.date <= (line.company_id.tax_lock_date or date.min): + raise ValidationError(_("The operation is refused as it would impact an already issued tax statement. " + + "Please change the journal entry date or the tax lock date set in the settings ({}) to proceed").format(line.company_id.tax_lock_date or date.min)) + @api.multi @api.constrains('currency_id', 'account_id') def _check_currency(self): @@ -1174,6 +1190,7 @@ class AccountMoveLine(models.Model): @api.multi def unlink(self): self._update_check() + self._check_tax_lock_date2() move_ids = set() for line in self: if line.move_id.id not in move_ids: @@ -1222,8 +1239,6 @@ class AccountMoveLine(models.Model): move_ids = set() for line in self: err_msg = _('Move name (id): %s (%s)') % (line.move_id.name, str(line.move_id.id)) - if line.move_id.state != 'draft': - raise UserError(_('You cannot do this modification on a posted journal entry, you can just change some non legal fields. You must revert the journal entry to cancel it.\n%s.') % err_msg) if line.reconciled and not (line.debit == 0 and line.credit == 0): raise UserError(_('You cannot do this modification on a reconciled entry. You can just change some non legal fields or you must unreconcile first.\n%s.') % err_msg) if line.move_id.id not in move_ids: diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 45f5934d497..16ecc7fa62d 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -39,6 +39,7 @@ class ResCompany(models.Model): fiscalyear_last_month = fields.Selection(MONTH_SELECTION, default='12', required=True) period_lock_date = fields.Date(string="Lock Date for Non-Advisers", help="Only users with the 'Adviser' role can edit accounts prior to and inclusive of this date. Use it for period locking inside an open fiscal year, for example.") fiscalyear_lock_date = fields.Date(string="Lock Date", help="No users, including Advisers, can edit accounts prior to and inclusive of this date. Use it for fiscal year locking for example.") + tax_lock_date = fields.Date("Tax Lock Date", help="No users can edit journal entries related to a tax prior and inclusive of this date.") transfer_account_id = fields.Many2one('account.account', domain=lambda self: [('reconcile', '=', True), ('user_type_id.id', '=', self.env.ref('account.data_account_type_current_assets').id), ('deprecated', '=', False)], string="Inter-Banks Transfer Account", help="Intermediary account used when moving money from a liquidity account to another") expects_chart_of_accounts = fields.Boolean(string='Expects a Chart of Accounts', default=True) diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py index 9da1f5fb668..e87ae79c27a 100644 --- a/addons/account/tests/__init__.py +++ b/addons/account/tests/__init__.py @@ -3,6 +3,7 @@ from . import test_account_customer_invoice from . import test_account_move_closed_period +from . import test_account_move_tax_lock_date from . import test_account_supplier_invoice from . import test_account_validate_account_move from . import test_account_invoice_rounding diff --git a/addons/account/tests/test_account_move_tax_lock_date.py b/addons/account/tests/test_account_move_tax_lock_date.py new file mode 100644 index 00000000000..0f119741088 --- /dev/null +++ b/addons/account/tests/test_account_move_tax_lock_date.py @@ -0,0 +1,115 @@ +from odoo.addons.account.tests.account_test_classes import AccountingTestCase +from odoo.exceptions import ValidationError, UserError +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta +from calendar import monthrange +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo.tests import tagged + + +@tagged('post_install', '-at_install') +class TestTaxBlockDate(AccountingTestCase): + """ + Forbid creation, edition and deletion of Journal Items related to taxes with + a date prior to the Tax Block Date. + """ + + def setUp(self): + super(TestTaxBlockDate, self).setUp() + self.user_id = self.env.user + + last_day_month = datetime.now() + last_day_month = last_day_month.replace(day=monthrange(last_day_month.year, last_day_month.month)[1]) + self.last_day_month_str = last_day_month.strftime(DEFAULT_SERVER_DATE_FORMAT) + first_day_month = datetime.now() + first_day_month = first_day_month.replace(day=1) + self.first_day_month_str = first_day_month.strftime(DEFAULT_SERVER_DATE_FORMAT) + middle_day_month = datetime.now() + middle_day_month = middle_day_month.replace(day=15) + self.middle_day_month_str = middle_day_month.strftime(DEFAULT_SERVER_DATE_FORMAT) + + self.sale_journal_id = self.env['account.journal'].search([('type', '=', 'sale')], limit=1)[0] + self.account_id = self.env['account.account'].search([('internal_type', '=', 'receivable')], limit=1)[0] + self.other_account_id = self.env['account.account'].search([('internal_type', '!=', 'receivable')], limit=1)[0] + self.tax_id = self.env['account.tax'].search([], limit=1)[0] + + self.move = { + 'name': '/', + 'journal_id': self.sale_journal_id.id, + 'date': self.middle_day_month_str, + 'line_ids': [(0, 0, { + 'name': 'foo', + 'debit': 10, + 'account_id': self.account_id.id, + 'tax_ids': [(6, False, [self.tax_id.id])] + }), (0, 0, { + 'name': 'bar', + 'credit': 10, + 'account_id': self.account_id.id, + })] + } + + def test_create_before_block_date(self): + """ + Checks that you cannot create an account.move with a date before the tax + lock date + """ + self.user_id.company_id.tax_lock_date = self.last_day_month_str + with self.assertRaises(ValidationError): + move = self.env['account.move'].create(self.move) + move.post() + + def test_change_after_block_date(self): + """ + Checks that you can change an account.move with a date after the tax + lock date + """ + self.user_id.company_id.tax_lock_date = self.first_day_month_str + move = self.env['account.move'].create(self.move) + move.line_ids[0].write({'account_id': self.other_account_id.id}) + move.line_ids[1].write({'account_id': self.other_account_id.id}) + move.line_ids[1].write({'debit': 11}) + move.line_ids[0].write({'credit': 11}) + move.post() + move.line_ids[1].write({'tax_ids': [(5, False, False)]}) + + def test_change_before_block_date(self): + """ + Checks that you cannot change an account.move with a date before the tax + lock date + """ + self.user_id.company_id.tax_lock_date = self.first_day_month_str + move = self.env['account.move'].create(self.move) + self.user_id.company_id.tax_lock_date = self.last_day_month_str + move.line_ids[0].write({'account_id': self.other_account_id.id}) + move.line_ids[1].write({'account_id': self.other_account_id.id}) + with self.assertRaises(ValidationError): + with self.cr.savepoint(): + move.line_ids[1].write({'debit': 11}) + with self.assertRaises(ValidationError): + with self.cr.savepoint(): + move.line_ids[1].write({'date': self.last_day_month_str, 'tax_ids': [(5, False, False)]}) + move.line_ids[0].write({'credit': 10}) + move.post() + + def test_unlink_before_block_date(self): + """ + Checks that you cannot unlink an account.move with a date before the tax + lock date + """ + self.user_id.company_id.tax_lock_date = self.first_day_month_str + move = self.env['account.move'].create(self.move) + move.post() + self.user_id.company_id.tax_lock_date = self.last_day_month_str + with self.assertRaises(ValidationError): + move.unlink() + + def test_unlink_after_block_date(self): + """ + Checks that you can unlink an account.move with a date after the lock + date + """ + self.user_id.company_id.tax_lock_date = self.first_day_month_str + move = self.env['account.move'].create(self.move) + move.post() + move.unlink()