feat: Expand valid account types for customer income and expense accounts to include receivable, payable, and direct cost types, and update Odoo version in manifest.
This commit is contained in:
parent
5ddb93e2ec
commit
64ed12822e
@ -5,7 +5,7 @@
|
||||
'summary': "Customer-specific income and expense accounts for sales transactions",
|
||||
|
||||
'description': """
|
||||
This module extends Odoo 18's accounting functionality to support customer-specific
|
||||
This module extends Odoo 19's accounting functionality to support customer-specific
|
||||
income and expense accounts for sales transactions. By default, Odoo creates journal
|
||||
entries using the income and expense accounts defined in the product category. This
|
||||
module allows defining income and expense accounts at the customer level, which take
|
||||
|
||||
@ -102,5 +102,3 @@ class AccountMoveLine(models.Model):
|
||||
"Using standard product category account.",
|
||||
partner.name, partner.id, line.move_id.name or 'draft', str(e)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ class ResPartner(models.Model):
|
||||
'account.account',
|
||||
company_dependent=True,
|
||||
string='Customer Income Account',
|
||||
domain="[('account_type', '=', 'income'), ('active', '=', True)]",
|
||||
domain="[('account_type', 'in', ('asset_receivable', 'liability_payable', 'income', 'income_other', 'expense', 'expense_direct_cost')), ('active', '=', True)]",
|
||||
help="This account will be used for revenue entries when selling to this customer. "
|
||||
"If not set, the income account from the product category will be used."
|
||||
)
|
||||
@ -24,11 +24,13 @@ class ResPartner(models.Model):
|
||||
'account.account',
|
||||
company_dependent=True,
|
||||
string='Customer Expense Account (COGS)',
|
||||
domain="[('account_type', '=', 'expense'), ('active', '=', True)]",
|
||||
domain="[('account_type', 'in', ('asset_receivable', 'liability_payable', 'income', 'income_other', 'expense', 'expense_direct_cost')), ('active', '=', True)]",
|
||||
help="This account will be used for COGS entries when selling to this customer. "
|
||||
"If not set, the expense account from the product category will be used."
|
||||
)
|
||||
|
||||
|
||||
|
||||
def _get_customer_income_account(self):
|
||||
"""
|
||||
Returns the customer-specific income account if defined.
|
||||
@ -144,16 +146,17 @@ class ResPartner(models.Model):
|
||||
@api.constrains('property_account_income_customer_id')
|
||||
def _check_income_account(self):
|
||||
"""
|
||||
Validate that the customer income account is of type 'income',
|
||||
Validate that the customer income account is of a valid type,
|
||||
not deprecated, and belongs to the correct company.
|
||||
"""
|
||||
valid_types = ('asset_receivable', 'liability_payable', 'income', 'income_other', 'expense', 'expense_direct_cost')
|
||||
for partner in self:
|
||||
account = partner.property_account_income_customer_id
|
||||
if account:
|
||||
# Check account type
|
||||
if account.account_type != 'income':
|
||||
if account.account_type not in valid_types:
|
||||
raise ValidationError(_(
|
||||
"The selected income account '%s' must be of type 'Income'. "
|
||||
"The selected income account '%s' must be of type 'Income', 'Receivable', 'Payable', or 'Cost of Revenue'. "
|
||||
"Please select a valid income account."
|
||||
) % account.display_name)
|
||||
|
||||
@ -175,16 +178,17 @@ class ResPartner(models.Model):
|
||||
@api.constrains('property_account_expense_customer_id')
|
||||
def _check_expense_account(self):
|
||||
"""
|
||||
Validate that the customer expense account is of type 'expense',
|
||||
Validate that the customer expense account is of a valid type,
|
||||
not deprecated, and belongs to the correct company.
|
||||
"""
|
||||
valid_types = ('asset_receivable', 'liability_payable', 'income', 'income_other', 'expense', 'expense_direct_cost')
|
||||
for partner in self:
|
||||
account = partner.property_account_expense_customer_id
|
||||
if account:
|
||||
# Check account type
|
||||
if account.account_type != 'expense':
|
||||
if account.account_type not in valid_types:
|
||||
raise ValidationError(_(
|
||||
"The selected expense account '%s' must be of type 'Expense'. "
|
||||
"The selected expense account '%s' must be of type 'Expense', 'Cost of Revenue', 'Receivable', or 'Payable'. "
|
||||
"Please select a valid expense account."
|
||||
) % account.display_name)
|
||||
|
||||
|
||||
@ -82,7 +82,7 @@ class TestAccountValidation(TransactionCase):
|
||||
@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']),
|
||||
account_type=st.sampled_from(['asset_current', 'liability_current', 'equity']),
|
||||
)
|
||||
def test_property_10_income_account_type_validation(self, customer_name, account_type):
|
||||
"""
|
||||
@ -98,7 +98,6 @@ class TestAccountValidation(TransactionCase):
|
||||
'asset_current': self.asset_account,
|
||||
'liability_current': self.liability_account,
|
||||
'equity': self.equity_account,
|
||||
'expense': self.expense_account,
|
||||
}
|
||||
|
||||
wrong_account = account_map[account_type]
|
||||
@ -122,7 +121,7 @@ class TestAccountValidation(TransactionCase):
|
||||
@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']),
|
||||
account_type=st.sampled_from(['asset_current', 'liability_current', 'equity']),
|
||||
)
|
||||
def test_property_11_expense_account_type_validation(self, customer_name, account_type):
|
||||
"""
|
||||
@ -138,7 +137,6 @@ class TestAccountValidation(TransactionCase):
|
||||
'asset_current': self.asset_account,
|
||||
'liability_current': self.liability_account,
|
||||
'equity': self.equity_account,
|
||||
'income': self.income_account,
|
||||
}
|
||||
|
||||
wrong_account = account_map[account_type]
|
||||
|
||||
@ -124,12 +124,22 @@ class TestCustomerFormView(TransactionCase):
|
||||
self.assertIn(
|
||||
'income',
|
||||
domain_str,
|
||||
"Income account field domain should filter for income type accounts"
|
||||
"Income account field domain should include income type accounts"
|
||||
)
|
||||
self.assertIn(
|
||||
'deprecated',
|
||||
'asset_receivable',
|
||||
domain_str,
|
||||
"Income account field domain should filter out deprecated accounts"
|
||||
"Income account field domain should include receivable type accounts"
|
||||
)
|
||||
self.assertIn(
|
||||
'expense_direct_cost',
|
||||
domain_str,
|
||||
"Income account field domain should include cost of revenue type accounts"
|
||||
)
|
||||
self.assertIn(
|
||||
'active',
|
||||
domain_str,
|
||||
"Income account field domain should filter out inactive accounts"
|
||||
)
|
||||
|
||||
def test_expense_account_field_domain(self):
|
||||
@ -160,12 +170,22 @@ class TestCustomerFormView(TransactionCase):
|
||||
self.assertIn(
|
||||
'expense',
|
||||
domain_str,
|
||||
"Expense account field domain should filter for expense type accounts"
|
||||
"Expense account field domain should include expense type accounts"
|
||||
)
|
||||
self.assertIn(
|
||||
'deprecated',
|
||||
'liability_payable',
|
||||
domain_str,
|
||||
"Expense account field domain should filter out deprecated accounts"
|
||||
"Expense account field domain should include payable type accounts"
|
||||
)
|
||||
self.assertIn(
|
||||
'expense_direct_cost',
|
||||
domain_str,
|
||||
"Expense account field domain should include cost of revenue type accounts"
|
||||
)
|
||||
self.assertIn(
|
||||
'active',
|
||||
domain_str,
|
||||
"Expense account field domain should filter out inactive accounts"
|
||||
)
|
||||
|
||||
def test_fields_are_optional(self):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user