# -*- coding: utf-8 -*- from odoo.tests import tagged from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError, UserError from hypothesis import given, strategies as st, settings, assume @tagged('post_install', '-at_install') class TestAccountValidation(TransactionCase): """ Property-based tests for account validation on res.partner model. Tests Properties 10, 11, and 12 from the design document. """ @classmethod def setUpClass(cls): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) # Create test company cls.company = cls.env['res.company'].create({ 'name': 'Test Validation Company', }) # Create accounts of different types for testing # Note: In Odoo 18, account.account uses company_ids (Many2many), not company_id # We need to set company_ids to match the partner's company for validation to pass cls.income_account = cls.env['account.account'].with_company(cls.company).create({ 'name': 'Valid Income Account', 'code': 'VINC001', 'account_type': 'income', 'company_ids': [(6, 0, [cls.company.id])], }) cls.expense_account = cls.env['account.account'].with_company(cls.company).create({ 'name': 'Valid Expense Account', 'code': 'VEXP001', 'account_type': 'expense', 'company_ids': [(6, 0, [cls.company.id])], }) # Create accounts of wrong types cls.asset_account = cls.env['account.account'].with_company(cls.company).create({ 'name': 'Asset Account', 'code': 'ASSET001', 'account_type': 'asset_current', 'company_ids': [(6, 0, [cls.company.id])], }) cls.liability_account = cls.env['account.account'].with_company(cls.company).create({ 'name': 'Liability Account', 'code': 'LIAB001', 'account_type': 'liability_current', 'company_ids': [(6, 0, [cls.company.id])], }) cls.equity_account = cls.env['account.account'].with_company(cls.company).create({ 'name': 'Equity Account', 'code': 'EQUITY001', 'account_type': 'equity', 'company_ids': [(6, 0, [cls.company.id])], }) # Create deprecated accounts cls.deprecated_income_account = cls.env['account.account'].with_company(cls.company).create({ 'name': 'Deprecated Income Account', 'code': 'DINC001', 'account_type': 'income', 'deprecated': True, 'company_ids': [(6, 0, [cls.company.id])], }) cls.deprecated_expense_account = cls.env['account.account'].with_company(cls.company).create({ 'name': 'Deprecated Expense Account', 'code': 'DEXP001', 'account_type': 'expense', 'deprecated': True, 'company_ids': [(6, 0, [cls.company.id])], }) @settings(max_examples=100) @given( customer_name=st.text(min_size=1, max_size=50, alphabet=st.characters(blacklist_categories=('Cs', 'Cc'))), account_type=st.sampled_from(['asset_current', 'liability_current', 'equity', 'expense']), ) def test_property_10_income_account_type_validation(self, customer_name, account_type): """ **Feature: customer-cogs-expense-account, Property 10: Income account type validation** For any customer record, attempting to set an income account field to a non-income account type should trigger a validation error. **Validates: Requirements 4.1, 4.6** """ # Map account types to test accounts account_map = { 'asset_current': self.asset_account, 'liability_current': self.liability_account, 'equity': self.equity_account, 'expense': self.expense_account, } wrong_account = account_map[account_type] # Create a partner partner = self.env['res.partner'].with_company(self.company).create({ 'name': customer_name, 'company_id': self.company.id, }) # Attempt to set a non-income account as income account # This should raise a ValidationError with self.assertRaises( ValidationError, msg=f"Setting income account to {account_type} type should raise ValidationError" ): partner.write({ 'property_account_income_customer_id': wrong_account.id, }) @settings(max_examples=100) @given( customer_name=st.text(min_size=1, max_size=50, alphabet=st.characters(blacklist_categories=('Cs', 'Cc'))), account_type=st.sampled_from(['asset_current', 'liability_current', 'equity', 'income']), ) def test_property_11_expense_account_type_validation(self, customer_name, account_type): """ **Feature: customer-cogs-expense-account, Property 11: Expense account type validation** For any customer record, attempting to set an expense account field to a non-expense account type should trigger a validation error. **Validates: Requirements 4.2, 4.7** """ # Map account types to test accounts account_map = { 'asset_current': self.asset_account, 'liability_current': self.liability_account, 'equity': self.equity_account, 'income': self.income_account, } wrong_account = account_map[account_type] # Create a partner partner = self.env['res.partner'].with_company(self.company).create({ 'name': customer_name, 'company_id': self.company.id, }) # Attempt to set a non-expense account as expense account # This should raise a ValidationError with self.assertRaises( ValidationError, msg=f"Setting expense account to {account_type} type should raise ValidationError" ): partner.write({ 'property_account_expense_customer_id': wrong_account.id, }) @settings(max_examples=100) @given( customer_name=st.text(min_size=1, max_size=50, alphabet=st.characters(blacklist_categories=('Cs', 'Cc'))), ) def test_property_12_active_account_validation_income(self, customer_name): """ **Feature: customer-cogs-expense-account, Property 12: Active account validation** For any journal entry creation, using an inactive or invalid customer-specific account should prevent entry creation and display an error. This test validates the income account scenario. **Validates: Requirements 4.3, 4.4** """ # Test 1: Deprecated income account should not be assignable at creation time partner = self.env['res.partner'].with_company(self.company).create({ 'name': customer_name, 'company_id': self.company.id, }) # Attempt to set a deprecated income account with self.assertRaises( ValidationError, msg="Setting deprecated income account should raise ValidationError" ): partner.write({ 'property_account_income_customer_id': self.deprecated_income_account.id, }) # Test 2: Runtime validation - account becomes deprecated after assignment # Create partner with valid income account partner_runtime = self.env['res.partner'].with_company(self.company).create({ 'name': customer_name + '_runtime', 'company_id': self.company.id, 'property_account_income_customer_id': self.income_account.id, }) # Simulate account becoming deprecated self.income_account.write({'deprecated': True}) # Attempting to retrieve the account should raise UserError with self.assertRaises( UserError, msg="Retrieving deprecated income account should raise UserError" ): partner_runtime._get_customer_income_account() # Restore account state for other tests self.income_account.write({'deprecated': False}) @settings(max_examples=100) @given( customer_name=st.text(min_size=1, max_size=50, alphabet=st.characters(blacklist_categories=('Cs', 'Cc'))), ) def test_property_12_active_account_validation_expense(self, customer_name): """ **Feature: customer-cogs-expense-account, Property 12: Active account validation** For any journal entry creation, using an inactive or invalid customer-specific account should prevent entry creation and display an error. This test validates the expense account scenario. **Validates: Requirements 4.3, 4.4** """ # Test 1: Deprecated expense account should not be assignable at creation time partner = self.env['res.partner'].with_company(self.company).create({ 'name': customer_name, 'company_id': self.company.id, }) # Attempt to set a deprecated expense account with self.assertRaises( ValidationError, msg="Setting deprecated expense account should raise ValidationError" ): partner.write({ 'property_account_expense_customer_id': self.deprecated_expense_account.id, }) # Test 2: Runtime validation - account becomes deprecated after assignment # Create partner with valid expense account partner_runtime = self.env['res.partner'].with_company(self.company).create({ 'name': customer_name + '_runtime', 'company_id': self.company.id, 'property_account_expense_customer_id': self.expense_account.id, }) # Simulate account becoming deprecated self.expense_account.write({'deprecated': True}) # Attempting to retrieve the account should raise UserError with self.assertRaises( UserError, msg="Retrieving deprecated expense account should raise UserError" ): partner_runtime._get_customer_expense_account() # Restore account state for other tests self.expense_account.write({'deprecated': False}) def test_valid_accounts_accepted(self): """ Test that valid income and expense accounts are accepted without errors. This is a sanity check to ensure validation doesn't reject valid accounts. """ # Create partner with valid accounts partner = self.env['res.partner'].with_company(self.company).create({ 'name': 'Valid Partner', 'company_id': self.company.id, 'property_account_income_customer_id': self.income_account.id, 'property_account_expense_customer_id': self.expense_account.id, }) # Verify accounts are set correctly self.assertEqual( partner.property_account_income_customer_id.id, self.income_account.id, "Valid income account should be accepted" ) self.assertEqual( partner.property_account_expense_customer_id.id, self.expense_account.id, "Valid expense account should be accepted" ) # Verify helper methods work correctly self.assertEqual( partner._get_customer_income_account().id, self.income_account.id, "Helper method should return valid income account" ) self.assertEqual( partner._get_customer_expense_account().id, self.expense_account.id, "Helper method should return valid expense account" )