Odoo18-Base/addons/account/tests/test_account_lock_exception.py

422 lines
21 KiB
Python
Raw Permalink Normal View History

2025-01-06 10:57:38 +07:00
from datetime import timedelta
from freezegun import freeze_time
from odoo import Command, fields
from odoo.addons.account.models.company import SOFT_LOCK_DATE_FIELDS
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.exceptions import UserError
from odoo.tests import new_test_user, tagged
@tagged('post_install', '-at_install')
class TestAccountLockException(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.fakenow = cls.env.cr.now()
cls.startClassPatcher(freeze_time(cls.fakenow))
cls.other_user = new_test_user(
cls.env,
name='Other User',
login='other_user',
password='password',
email='other_user@example.com',
groups_id=cls.get_default_groups().ids,
company_id=cls.env.company.id,
)
cls.company_data_2 = cls.setup_other_company()
cls.soft_lock_date_info = [
('fiscalyear_lock_date', 'out_invoice'),
('tax_lock_date', 'out_invoice'),
('sale_lock_date', 'out_invoice'),
('purchase_lock_date', 'in_invoice'),
]
def test_user_exception_move_edit_multi_user(self):
"""
Test that an exception for a specific user only works for that user.
"""
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add an exception to make the move editable (for the current user)
self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_move_edit_multi_user',
})
move.button_draft()
move.action_post()
# Check that the exception does not apply to other users
with self.assertRaises(UserError), self.cr.savepoint():
move.with_user(self.other_user).button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_global_exception_move_edit_multi_user(self):
"""
Test that an exception without a specified user works for any user.
"""
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add a global exception to make the move editable for everyone
self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': False,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_global_exception_move_edit_multi_user',
})
move.button_draft()
move.action_post()
move.with_user(self.other_user).button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_user_exception_branch(self):
"""
Test that the locking and exception mechanism works correctly in company hierarchies.
* A lock in the branch does not lock the parent.
* A lock in the parent also locks the branch.
* An exception in the branch does not matter for the lock in the parent.
* Let both parent and branch be locked.
To make changes in the locked period in the brranch we need exceptions in both companies.
"""
root_company = self.company_data['company']
root_company.write({'child_ids': [Command.create({'name': 'branch'})]})
self.cr.precommit.run() # load the CoA
branch = root_company.child_ids
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
# Create a move in the branch
branch_move = self.init_invoice(
move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a, company=branch,
)
# Create a move in the parent company
root_move = self.init_invoice(
move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a, company=root_company,
)
# Lock the branch
branch[lock_date_field] = fields.Date.to_date('2020-01-01')
# The branch_move is locked while the root_move is not
with self.assertRaises(UserError), self.cr.savepoint():
branch_move.button_draft()
root_move.button_draft()
root_move.action_post()
# Add an exception in the branch to make the branch_move editable (for the current user)
self.env['account.lock_exception'].create({
'company_id': branch.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_branch branch exception',
})
branch_move.button_draft()
branch_move.action_post()
# Lock the parent company
root_company[lock_date_field] = fields.Date.to_date('2020-01-01')
# Check that both moves are locked now (the branch exception alone is insufficient)
for move in [branch_move, root_move]:
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add an exception in the parent company to make both moves editable (for the current user)
self.env['account.lock_exception'].create({
'company_id': root_company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_branch root_company exception',
})
for move in [branch_move, root_move]:
move.button_draft()
move.action_post()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_user_exception_wrong_company(self):
"""
Test that an exception only works for the specified company.
"""
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add an exception for another company
self.env['account.lock_exception'].create({
'company_id': self.company_data_2['company'].id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_move_edit_multi_user',
})
# Check that the exception is insufficient
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_user_exception_insufficient(self):
"""
Test that the exception only works if the specified lock date is actually before the accounting date.
"""
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add an exception before the lock date but not before the date of the test_invoice
self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2016-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_move_edit_multi_user',
})
# Check that the exception is insufficient
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_expired_exception(self):
"""
Test that the exception does not work if we are past the `end_datetime` of the exception.
"""
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add an expired exception
self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'create_date': self.fakenow - timedelta(hours=24),
'end_datetime': self.fakenow - timedelta(milliseconds=1),
'reason': 'test_expired_exception',
})
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_revoked_exception(self):
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add an exception to make the move editable (for the current user)
exception = self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_move_edit_multi_user',
})
move.button_draft()
move.action_post()
exception.action_revoke()
# Check that the exception does not work anymore
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_user_exception_wrong_field(self):
for lock_date_field, move_type, exception_lock_date_field in [
('fiscalyear_lock_date', 'out_invoice', 'tax_lock_date'),
('tax_lock_date', 'out_invoice', 'fiscalyear_lock_date'),
('sale_lock_date', 'out_invoice', 'purchase_lock_date'),
('purchase_lock_date', 'in_invoice', 'sale_lock_date'),
]:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
# Add an exception for a different lock date field
self.env['account.lock_exception'].create({
'company_id': self.company_data_2['company'].id,
'user_id': self.env.user.id,
exception_lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_wrong_field',
})
# Check that the exception is insufficient
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation
def test_hard_lock_date(self):
"""
Test that
* exceptions (for other lock date fields) do not allow bypassing the hard lock date
* the hard lock date cannot be decreased or removed
"""
in_move = self.init_invoice('in_invoice', invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
out_move = self.init_invoice('out_invoice', invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
self.company.hard_lock_date = fields.Date.to_date('2020-01-01')
# Check that we cannot remove the hard lock date.
with self.assertRaises(UserError), self.cr.savepoint():
self.company.hard_lock_date = False
# Check that we cannot decrease the hard lock date.
with self.assertRaises(UserError), self.cr.savepoint():
self.company.hard_lock_date = fields.Date.to_date('2019-01-01')
# Create exceptions for all lock date fields except the hard lock date
self.env['account.lock_exception'].create([
{
'company_id': self.company_data_2['company'].id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': f'test_hard_lock_ignores_exceptions {lock_date_field}',
}
for lock_date_field in SOFT_LOCK_DATE_FIELDS
])
# Check that the exceptions are insufficient
for move in [in_move, out_move]:
with self.assertRaises(UserError), self.cr.savepoint():
move.button_draft()
def test_company_lock_date(self):
"""
Test the `company_lock_date` field is set corretly on exception creation.
Test the behavior when a company lock date is changed.
* Every active exception gets revoked and recreated with the new company lock date
* Non-active exceptions are not affected
"""
self.env['account.lock_exception'].search([]).sudo().unlink()
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
revoked_exception = self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_exception_recreated_on_lock_date_change revoked',
})
revoked_exception.action_revoke()
active_exception = self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_exception_recreated_on_lock_date_change active',
})
# Check that the company lock date field was set correcyly on exception creation
self.assertEqual(revoked_exception.company_lock_date, fields.Date.to_date('2020-01-01'))
self.assertEqual(active_exception.company_lock_date, fields.Date.to_date('2020-01-01'))
# The lock date change should trigger the "recreation" proces
self.company[lock_date_field] = fields.Date.to_date('2021-01-01')
self.assertEqual(revoked_exception.company_lock_date, fields.Date.to_date('2020-01-01'))
self.assertEqual(active_exception.state, 'revoked')
exceptions = self.env['account.lock_exception'].with_context(active_test=False).search([])
self.assertEqual(len(exceptions), 3)
new_exception = exceptions - revoked_exception - active_exception
# Check that the new exception is a "recreation" of the `active_exception`
self.assertRecordValues(new_exception, [{
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: fields.Date.to_date('2010-01-01'),
'company_lock_date': fields.Date.to_date('2021-01-01'),
'end_datetime': self.env.cr.now() + timedelta(hours=24),
'reason': 'test_exception_recreated_on_lock_date_change active',
}])
sp.close() # Rollback to ensure all subtests start in the same situation
def test_user_exception_remove_lock_date(self):
"""
Test that an exception removing a lock date (instead of just decreasing it) works.
"""
for lock_date_field, move_type in self.soft_lock_date_info:
with self.subTest(lock_date_field=lock_date_field, move_type=move_type), self.cr.savepoint() as sp:
move = self.init_invoice(move_type, invoice_date='2016-01-01', post=True, amounts=[1000.0], taxes=self.tax_sale_a)
# Lock the move
self.company[lock_date_field] = fields.Date.to_date('2020-01-01')
with self.assertRaises(UserError):
move.button_draft()
# Add an exception removing the lock date
self.env['account.lock_exception'].create({
'company_id': self.company.id,
'user_id': self.env.user.id,
lock_date_field: False,
'end_datetime': self.fakenow + timedelta(hours=24),
'reason': 'test_user_exception_move_edit_multi_user',
})
move.button_draft()
sp.close() # Rollback to ensure all subtests start in the same situation