Files
tryton/modules/account_product/product.py
T
Cédric Krier a6e822f877 Depends on xxx2many fields when they are assigned by a change function
Closes #13099
(grafted from 4a24c323d44a3b5ab440b1bafc4b3b9005500642)

--HG--
branch : 7.0
2024-04-22 08:59:48 +02:00

430 lines
15 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from functools import wraps
from sql import Null
from trytond.i18n import gettext
from trytond.model import ModelSQL, fields
from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, Or
from trytond.transaction import Transaction
from .exceptions import AccountError, TaxError
def account_used(field_name, field_string=None):
def decorator(func):
@wraps(func)
def wrapper(self):
account = func(self)
if not account:
account = self.get_account(field_name + '_used')
# Allow empty values on on_change
if not account and not Transaction().readonly:
Model = self.__class__
field = field_name
if field_string:
if getattr(self, field_string, None):
Model = getattr(self, field_string).__class__
else:
field = field_string
field = (
Model.fields_get([field])[field]['string'])
raise AccountError(
gettext('account_product.msg_missing_account',
field=field,
name=self.rec_name))
if account:
return account.current()
return wrapper
return decorator
def template_property(field_name):
@property
@fields.depends('template')
def prop(self):
return getattr(self.template, field_name)
return prop
class Category(CompanyMultiValueMixin, metaclass=PoolMeta):
__name__ = 'product.category'
accounting = fields.Boolean(
"Accounting",
states={
'readonly': Bool(Eval('childs', [0])) | Bool(Eval('parent')),
},
help="Check to indicate the category is used for accounting.")
account_parent = fields.Boolean('Use Parent\'s accounts',
states={
'invisible': ~Eval('accounting', False),
},
help="Use the accounts defined on the parent category.")
accounts = fields.One2Many(
'product.category.account', 'category', "Accounts")
account_expense = fields.MultiValue(fields.Many2One('account.account',
'Account Expense', domain=[
('closed', '!=', True),
('type.expense', '=', True),
('company', '=', Eval('context', {}).get('company', -1)),
],
states={
'invisible': (~Eval('context', {}).get('company')
| Eval('account_parent')
| ~Eval('accounting', False)),
}))
account_revenue = fields.MultiValue(fields.Many2One('account.account',
'Account Revenue', domain=[
('closed', '!=', True),
('type.revenue', '=', True),
('company', '=', Eval('context', {}).get('company', -1)),
],
states={
'invisible': (~Eval('context', {}).get('company')
| Eval('account_parent')
| ~Eval('accounting', False)),
}))
taxes_parent = fields.Boolean('Use the Parent\'s Taxes',
states={
'invisible': ~Eval('accounting', False),
},
help="Use the taxes defined on the parent category.")
customer_taxes = fields.Many2Many('product.category-customer-account.tax',
'category', 'tax', 'Customer Taxes',
order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
domain=[('parent', '=', None), ['OR',
('group', '=', None),
('group.kind', 'in', ['sale', 'both'])],
],
states={
'invisible': (~Eval('context', {}).get('company')
| Eval('taxes_parent')
| ~Eval('accounting', False)),
},
help="The taxes to apply when selling products of this category.")
supplier_taxes = fields.Many2Many('product.category-supplier-account.tax',
'category', 'tax', 'Supplier Taxes',
order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
domain=[('parent', '=', None), ['OR',
('group', '=', None),
('group.kind', 'in', ['purchase', 'both'])],
],
states={
'invisible': (~Eval('context', {}).get('company')
| Eval('taxes_parent')
| ~Eval('accounting', False)),
},
help="The taxes to apply when purchasing products of this category.")
supplier_taxes_deductible_rate = fields.Numeric(
"Supplier Taxes Deductible Rate", digits=(14, 10),
domain=[
('supplier_taxes_deductible_rate', '>=', 0),
('supplier_taxes_deductible_rate', '<=', 1),
],
states={
'invisible': (
Eval('taxes_parent') | ~Eval('accounting', False)),
})
customer_taxes_used = fields.Function(fields.Many2Many(
'account.tax', None, None, "Customer Taxes Used"), 'get_taxes')
supplier_taxes_used = fields.Function(fields.Many2Many(
'account.tax', None, None, "Supplier Taxes Used"), 'get_taxes')
@classmethod
def __setup__(cls):
super(Category, cls).__setup__()
cls.parent.domain = [
('accounting', '=', Eval('accounting', False)),
cls.parent.domain or []]
cls.parent.states['required'] = Or(
cls.parent.states.get('required', False),
Eval('account_parent', False) | Eval('taxes_parent', False))
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field in {'account_expense', 'account_revenue'}:
return pool.get('product.category.account')
return super(Category, cls).multivalue_model(field)
@classmethod
def default_accounting(cls):
return False
@classmethod
def default_account_expense(cls, **pattern):
pool = Pool()
Configuration = pool.get('account.configuration')
config = Configuration(1)
account = config.get_multivalue(
'default_category_account_expense', **pattern)
return account.id if account else None
@classmethod
def default_account_revenue(cls, **pattern):
pool = Pool()
Configuration = pool.get('account.configuration')
config = Configuration(1)
account = config.get_multivalue(
'default_category_account_revenue', **pattern)
return account.id if account else None
@classmethod
def default_supplier_taxes_deductible_rate(cls):
return 1
def get_account(self, name, **pattern):
if self.account_parent:
return self.parent.get_account(name, **pattern)
else:
transaction = Transaction()
with transaction.reset_context(), \
transaction.set_context(self._context):
return self.get_multivalue(name[:-5], **pattern)
def get_taxes(self, name):
company = Transaction().context.get('company')
if self.taxes_parent:
return [x.id for x in getattr(self.parent, name)]
else:
return [x.id for x in getattr(self, name[:-5])
if x.company.id == company]
@fields.depends('parent', '_parent_parent.accounting', 'accounting')
def on_change_with_accounting(self):
if self.parent:
return self.parent.accounting
return self.accounting
@fields.depends(
'accounting',
'account_parent', 'account_expense', 'account_revenue',
'taxes_parent', 'customer_taxes', 'supplier_taxes')
def on_change_accounting(self):
if not self.accounting:
self.account_parent = None
self.account_expense = None
self.account_revenue = None
self.taxes_parent = None
self.customer_taxes = None
self.supplier_taxes = None
@fields.depends('account_expense', 'supplier_taxes')
def on_change_account_expense(self):
if self.account_expense:
self.supplier_taxes = self.account_expense.taxes
else:
self.supplier_taxes = []
@fields.depends('account_revenue', 'customer_taxes')
def on_change_account_revenue(self):
if self.account_revenue:
self.customer_taxes = self.account_revenue.taxes
else:
self.customer_taxes = []
@classmethod
def view_attributes(cls):
return super(Category, cls).view_attributes() + [
('/form/notebook/page[@id="accounting"]', 'states', {
'invisible': ~Eval('accounting', False),
}),
]
@property
@account_used('account_expense')
def account_expense_used(self):
pass
@property
@account_used('account_revenue')
def account_revenue_used(self):
pass
@property
def supplier_taxes_deductible_rate_used(self):
if self.taxes_parent:
return self.parent.supplier_taxes_deductible_rate_used
else:
return self.supplier_taxes_deductible_rate
class CategoryAccount(ModelSQL, CompanyValueMixin):
"Category Account"
__name__ = 'product.category.account'
category = fields.Many2One(
'product.category', "Category", ondelete='CASCADE',
context={
'company': Eval('company', -1),
},
depends={'company'})
account_expense = fields.Many2One(
'account.account', "Account Expense",
domain=[
('type.expense', '=', True),
('company', '=', Eval('company', -1)),
])
account_revenue = fields.Many2One(
'account.account', "Account Revenue",
domain=[
('type.revenue', '=', True),
('company', '=', Eval('company', -1)),
])
class CategoryCustomerTax(ModelSQL):
'Category - Customer Tax'
__name__ = 'product.category-customer-account.tax'
_table = 'product_category_customer_taxes_rel'
category = fields.Many2One(
'product.category', "Category", ondelete='CASCADE', required=True)
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
required=True)
@classmethod
def __setup__(cls):
super().__setup__()
cls.__access__.add('tax')
class CategorySupplierTax(ModelSQL):
'Category - Supplier Tax'
__name__ = 'product.category-supplier-account.tax'
_table = 'product_category_supplier_taxes_rel'
category = fields.Many2One(
'product.category', "Category", ondelete='CASCADE', required=True)
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
required=True)
@classmethod
def __setup__(cls):
super().__setup__()
cls.__access__.add('tax')
class Template(CompanyMultiValueMixin, metaclass=PoolMeta):
__name__ = 'product.template'
account_category = fields.Many2One('product.category', 'Account Category',
domain=[
('accounting', '=', True),
])
@fields.depends('account_category')
def get_account(self, name, **pattern):
if self.account_category:
return self.account_category.get_account(name, **pattern)
@fields.depends('account_category')
def get_taxes(self, name):
if self.account_category:
return getattr(self.account_category, name)
@property
@fields.depends('account_category', methods=['get_account'])
@account_used('account_expense', 'account_category')
def account_expense_used(self):
pass
@property
@fields.depends('account_category', methods=['get_account'])
@account_used('account_revenue', 'account_category')
def account_revenue_used(self):
pass
@property
@fields.depends(methods=['get_taxes', 'account_revenue_used'])
def customer_taxes_used(self):
taxes = self.get_taxes('customer_taxes_used')
if taxes is None:
account = self.account_revenue_used
if account:
taxes = account.taxes
if taxes is None:
# Allow empty values on on_change
if Transaction().readonly:
taxes = []
else:
raise TaxError(
gettext('account_product.msg_missing_taxes',
name=self.rec_name))
return taxes
@property
@fields.depends(methods=['get_taxes', 'account_expense_used'])
def supplier_taxes_used(self):
taxes = self.get_taxes('supplier_taxes_used')
if taxes is None:
account = self.account_expense_used
if account:
taxes = account.taxes
if taxes is None:
# Allow empty values on on_change
if Transaction().readonly:
taxes = []
else:
raise TaxError(
gettext('account_product.msg_missing_taxes',
name=self.rec_name))
return taxes
@property
@fields.depends(methods=['get_taxes'])
def supplier_taxes_deductible_rate_used(self):
return self.get_taxes('supplier_taxes_deductible_rate_used')
@classmethod
def copy(cls, templates, default=None):
default = default.copy() if default else {}
if Transaction().check_access:
default.setdefault(
'account_category',
cls.default_get(
['account_category'],
with_rec_name=False).get('account_category'))
return super().copy(templates, default=default)
class Product(metaclass=PoolMeta):
__name__ = 'product.product'
account_expense_used = template_property('account_expense_used')
account_revenue_used = template_property('account_revenue_used')
customer_taxes_used = template_property('customer_taxes_used')
supplier_taxes_used = template_property('supplier_taxes_used')
supplier_taxes_deductible_rate_used = template_property(
'supplier_taxes_deductible_rate_used')
class TemplateAccountCategory(ModelSQL):
"Template - Account Category"
__name__ = 'product.template-product.category.account'
template = fields.Many2One('product.template', 'Template')
category = fields.Many2One('product.category', 'Category')
@classmethod
def table_query(cls):
pool = Pool()
Template = pool.get('product.template')
template = Template.__table__()
return template.select(
template.id.as_('id'),
template.create_uid.as_('create_uid'),
template.create_date.as_('create_date'),
template.write_uid.as_('write_uid'),
template.write_date.as_('write_date'),
template.id.as_('template'),
template.account_category.as_('category'),
where=template.account_category != Null)
class TemplateCategoryAll(metaclass=PoolMeta):
__name__ = 'product.template-product.category.all'
@classmethod
def union_models(cls):
return super(TemplateCategoryAll, cls).union_models() + [
'product.template-product.category.account']