Distinct readonly and editable states

This commit is contained in:
Cédric Krier
2026-08-31 23:21:03 +02:00
parent e711ce7cb0
commit 56e1a24be4
59 changed files with 454 additions and 317 deletions
+3 -5
View File
@@ -111,13 +111,11 @@ def setup(config, activated, company):
if 'production_work' in activated:
for work in production.works:
for _ in range(0, random.randint(1, 2)):
cycle = WorkCycle(
work=work,
duration=dt.timedelta(
seconds=random.randint(60, 3600)),
)
cycle = WorkCycle(work=work)
cycle.save()
cycle.click('run')
cycle.duration = dt.timedelta(
seconds=random.randint(60, 3600))
cycle.click('do')
output, = production.outputs
output.unit_price = (production.cost
+12 -13
View File
@@ -629,8 +629,8 @@ def AccountMixin(template=False):
if (not isinstance(field, fields.Field)
or isinstance(field, fields.Function)):
continue
field.states['readonly'] = (
Bool(Eval('template', -1)) & ~Eval('template_override', False))
field.states['editable'] = (
Eval('template', None) & ~Eval('template_override', False))
return Mixin
@@ -828,8 +828,7 @@ class Account(
ModelSQL, ModelView):
__name__ = 'account.account'
_states = {
'readonly': (Bool(Eval('template', -1))
& ~Eval('template_override', False)),
'editable': Eval('template', None) & ~Eval('template_override', False),
}
company = fields.Many2One('company.company', 'Company', required=True,
ondelete="RESTRICT")
@@ -842,14 +841,14 @@ class Account(
('id', '!=', Eval('currency', -1)),
],
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': ~Eval('deferral', False),
},
help="Force all moves for this account to have this second currency.")
type = fields.Many2One(
'account.account.type', "Type", ondelete='RESTRICT',
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
},
domain=[
('company', '=', Eval('company', -1)),
@@ -857,10 +856,10 @@ class Account(
debit_type = fields.Many2One(
'account.account.type', "Debit Type", ondelete='RESTRICT',
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': (
~Eval('type') | Eval('credit_type')
| (_states['readonly']) & ~Eval('debit_type')),
| (_states['editable']) & ~Eval('debit_type')),
},
domain=[
('company', '=', Eval('company', -1)),
@@ -872,10 +871,10 @@ class Account(
credit_type = fields.Many2One(
'account.account.type', "Credit Type", ondelete='RESTRICT',
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': (
~Eval('type') | Eval('debit_type')
| (_states['readonly']) & ~Eval('credit_type')),
| (_states['editable']) & ~Eval('credit_type')),
},
domain=[
('company', '=', Eval('company', -1)),
@@ -939,7 +938,7 @@ class Account(
'account.account', "Replaced By",
domain=[('company', '=', Eval('company', -1))],
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': ~Eval('end_date'),
})
template = fields.Many2One('account.account.template', 'Template')
@@ -955,8 +954,8 @@ class Account(
super().__setup__()
for date in [cls.start_date, cls.end_date]:
date.states = {
'readonly': (Bool(Eval('template', -1))
& ~Eval('template_override', False)),
'editable': (
Eval('template', -1) & ~Eval('template_override', False)),
}
cls._order.insert(0, ('code', 'ASC'))
cls._order.insert(1, ('name', 'ASC'))
+4 -2
View File
@@ -942,8 +942,9 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
ondelete='CASCADE',
states={
'required': False,
'readonly': (((Eval('state') == 'valid') | _states['readonly'])
& Bool(Eval('move'))),
'readonly': (
((Eval('state') == 'valid') | _states['readonly'])
& (Eval('move') & (Eval('id', -1) >= 0))),
})
journal = fields.Function(fields.Many2One(
'account.journal', 'Journal',
@@ -1567,6 +1568,7 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
default = {}
else:
default = default.copy()
default.setdefault('state', 'draft')
default.setdefault('move', None)
default.setdefault('reconciliation', None)
default.setdefault('reconciliations_delegated', [])
+29 -18
View File
@@ -15,7 +15,7 @@ from trytond.modules.account.exceptions import AccountMissing
from trytond.modules.company import CompanyReport
from trytond.modules.currency.fields import Monetary
from trytond.pool import Pool
from trytond.pyson import Bool, Eval, If
from trytond.pyson import Eval, If
from trytond.tools import cached_property
from trytond.transaction import Transaction, check_access, without_check_access
from trytond.wizard import (
@@ -57,7 +57,8 @@ class Asset(Workflow, ModelSQL, ModelView):
number = fields.Char("Number", readonly=True)
product = fields.Many2One('product.product', 'Product', required=True,
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
context={
'company': Eval('company', None),
@@ -81,7 +82,8 @@ class Asset(Workflow, ModelSQL, ModelView):
],
],
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0])
})
customer_invoice_line = fields.Function(fields.Many2One(
'account.invoice.line', 'Customer Invoice Line'),
@@ -102,19 +104,21 @@ class Asset(Workflow, ModelSQL, ModelView):
quantity = fields.Float(
"Quantity", digits='unit',
states={
'readonly': (Bool(Eval('supplier_invoice_line', 1))
| Eval('lines', [0])
| (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('supplier_invoice_line', 1)
| Eval('lines', [0])),
})
unit = fields.Many2One('product.uom', 'Unit',
states={
'readonly': (Bool(Eval('product'))
| (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('product'),
})
value = Monetary(
"Value", currency='currency', digits='currency',
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
required=True,
help="The value of the asset when purchased.")
@@ -124,7 +128,8 @@ class Asset(Workflow, ModelSQL, ModelView):
('depreciated_amount', '<=', Eval('value')),
],
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
required=True,
help="The amount already depreciated at the start date.")
@@ -139,22 +144,26 @@ class Asset(Workflow, ModelSQL, ModelView):
('residual_value', '<=', Eval('depreciating_value')),
],
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
purchase_date = fields.Date('Purchase Date', states={
'readonly': (Bool(Eval('supplier_invoice_line', 1))
| Eval('lines', [0])
| (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('supplier_invoice_line', 1)
| Eval('lines', [0])),
},
required=True)
start_date = fields.Date('Start Date', states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
required=True,
domain=[('start_date', '<=', Eval('end_date', None))])
end_date = fields.Date('End Date',
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
required=True,
domain=[('end_date', '>=', Eval('start_date', None))])
@@ -162,7 +171,8 @@ class Asset(Workflow, ModelSQL, ModelView):
('linear', 'Linear'),
], 'Depreciation Method',
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
required=True)
frequency = fields.Selection([
@@ -171,7 +181,8 @@ class Asset(Workflow, ModelSQL, ModelView):
], 'Frequency',
required=True,
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
state = fields.Selection([
('draft', 'Draft'),
+4 -3
View File
@@ -93,7 +93,8 @@ class BudgetMixin(ChatMixin):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': Eval('company') & Eval('lines', [-1]),
'readonly': Eval('id', -1) >= 0,
'editable': ~Eval('lines', [-1]),
},
help="The company that the budget is associated with.")
lines = None
@@ -305,13 +306,13 @@ class Budget(BudgetMixin, ModelSQL, ModelView):
lines = fields.One2Many(
'account.budget.line', 'budget', "Lines",
states={
'readonly': Eval('id', -1) < 0,
'editable': Eval('id', -1) < 0,
},
order=[('left', 'ASC'), ('id', 'ASC')])
root_lines = fields.One2Many(
'account.budget.line', 'budget', "Lines",
states={
'readonly': Eval('id', -1) < 0,
'editable': Eval('id', -1) < 0,
},
filter=[
('parent', '=', None),
+22 -17
View File
@@ -75,9 +75,9 @@ class Invoice(
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (
_states['readonly']
| Eval('party', True)
'readonly': _states['readonly'],
'editable': ~(
Eval('party', True)
| Eval('lines', [0])),
},
context={
@@ -100,8 +100,9 @@ class Invoice(
('in', "Supplier"),
], "Type", required=True,
states={
'readonly': ((Eval('state') != 'draft')
| Eval('context', {}).get('type')
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('context', {}).get('type')
| (Eval('lines', [0]) & Eval('type'))),
})
type_name = fields.Function(fields.Char('Type'), 'get_type_name')
@@ -200,9 +201,8 @@ class Invoice(
domain=[('party', '=', Eval('party', -1))])
currency = fields.Many2One('currency.currency', 'Currency', required=True,
states={
'readonly': (
_states['readonly']
| (Eval('lines', [0]) & Eval('currency'))),
'readonly': _states['readonly'],
'editable': ~(Eval('lines', [0]) & Eval('currency')),
})
currency_date = fields.Function(fields.Date('Currency Date'),
'on_change_with_currency_date')
@@ -1692,6 +1692,7 @@ class Invoice(
default.setdefault('supplier_payment_reference')
default.setdefault('supplier_payment_reference_type')
default.setdefault('sequence')
default.setdefault('sequence_type_cache')
default.setdefault('move', None)
default.setdefault('additional_moves', None)
default.setdefault('cancel_move', None)
@@ -2459,7 +2460,8 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
'required': (~Eval('invoice_type') & Eval('party')
& Eval('currency') & Eval('company')),
'invisible': Bool(Eval('context', {}).get('standalone')),
'readonly': _states['readonly'] & Bool(Eval('invoice')),
'readonly': _states['readonly'],
'editable': ~Eval('invoice'),
})
invoice_party = fields.Function(
fields.Many2One(
@@ -2480,7 +2482,9 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
invoice_type = fields.Selection(
'get_invoice_types', "Invoice Type",
states={
'readonly': Eval('context', {}).get('type') | Eval('type'),
'editable': (
~Eval('context', {}).get('type')
& ~Eval('invoice_type')),
'required': ~Eval('invoice'),
})
party = fields.Many2One(
@@ -3241,7 +3245,8 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
invoice = fields.Many2One(
'account.invoice', "Invoice", ondelete='CASCADE', required=True,
states={
'readonly': _states['readonly'] & Bool(Eval('invoice')),
'readonly': _states['readonly'],
'editable': ~Eval('invoice'),
})
invoice_state = fields.Function(
fields.Selection('get_invoice_states', "Invoice State"),
@@ -3287,18 +3292,18 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
('company', '=', Eval('_parent_invoice', {}).get('company', 0)),
],
states={
'readonly': (
~Eval('manual', False) | ~Bool(Eval('invoice'))
| _states['readonly']),
'readonly': _states['readonly'],
'editable': Eval('manual', False) & Bool(Eval('invoice')),
},
depends={'invoice'})
legal_notice = fields.Text(
"Legal Notice",
states={
'readonly': (_states['readonly']
& ~Id('account', 'group_account_admin').in_(
'readonly': _states['readonly'],
'editable': (
Id('account', 'group_account_admin').in_(
Eval('context', {}).get('groups', []))
& ~Eval('context', {}).get('administrator', False)),
| Eval('context', {}).get('administrator', False)),
})
del _states
@@ -58,7 +58,7 @@ Create invoice::
... 'source_untaxed_amount': Decimal('100.00'),
... 'source_tax_amount': Decimal('10.00'),
... 'source_total_amount': Decimal('110.00'),
... }, invoice._context)
... }, {**invoice._context, '_check_access': False})
Try to validate::
+2 -1
View File
@@ -63,7 +63,8 @@ class InvoiceDeferred(Workflow, ModelSQL, ModelView):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (Eval('state') != 'draft') & Eval('invoice_line'),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('invoice_line'),
})
type = fields.Selection([
('out', "Customer"),
+1 -1
View File
@@ -57,7 +57,7 @@ class Category(CompanyMultiValueMixin, metaclass=PoolMeta):
accounting = fields.Boolean(
"Accounting",
states={
'readonly': Bool(Eval('childs', [0])) | Bool(Eval('parent')),
'editable': 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',
+10 -7
View File
@@ -79,7 +79,8 @@ class Statement(Workflow, ModelSQL, ModelView, ChatMixin):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
journal = fields.Many2One(
'account.statement.journal', "Journal", required=True,
@@ -87,7 +88,8 @@ class Statement(Workflow, ModelSQL, ModelView, ChatMixin):
('company', '=', Eval('company', -1)),
],
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
currency = fields.Function(fields.Many2One(
'currency.currency', "Currency"), 'on_change_with_currency')
@@ -786,10 +788,11 @@ class Line(origin_mixin(_states), sequence_ordered(), ModelSQL, ModelView):
states=_states,
context={'with_payment': False})
origin = fields.Many2One(
'account.statement.origin', 'Origin', readonly=True,
'account.statement.origin', 'Origin',
ondelete='RESTRICT',
states={
'invisible': ~Bool(Eval('origin')),
'readonly': Eval('id', -1) >= 0,
},
domain=[
('statement', '=', Eval('statement', -1)),
@@ -802,9 +805,8 @@ class Line(origin_mixin(_states), sequence_ordered(), ModelSQL, ModelView):
def __setup__(cls):
super().__setup__()
table = cls.__table__()
cls.date.states['readonly'] = (
cls.date.states.get('readonly', False)
| Bool(Eval('origin', 0)))
cls.date.states['readonly'] = cls.date.states.get('readonly', False)
cls.date.states['editable'] = ~Bool(Eval('origin', 0))
cls.account.required = True
cls.party.states['required'] = (
cls.party.states.get('required', False)
@@ -1184,7 +1186,8 @@ class LineGroup(ModelSQL, ModelView):
_states = {
'readonly': (Eval('statement_state') != 'draft') | Eval('lines', []),
'readonly': Eval('statement_state') != 'draft',
'editable': ~Eval('lines', []),
}
+2 -2
View File
@@ -3,7 +3,7 @@
from trytond.model import DeactivableMixin, ModelSQL, ModelView, Unique, fields
from trytond.pool import PoolMeta
from trytond.pyson import Bool, Eval, If
from trytond.pyson import Eval, If
from trytond.transaction import Transaction
@@ -75,7 +75,7 @@ class Product_EUExciseTax(ModelSQL, ModelView):
country = fields.Many2One(
'country.country', "Country", required=True,
states={
'readonly': Bool(Eval('excise_tax')),
'editable': ~Eval('excise_tax'),
})
@classmethod
+7 -4
View File
@@ -29,7 +29,8 @@ class Sale(metaclass=PoolMeta):
],
states={
'invisible': ~Eval('warehouse_eu_excise_number'),
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
eu_excise_types = fields.Function(
fields.MultiSelection(IDENTIFIER_TYPES, "Excise Types"),
@@ -41,9 +42,11 @@ class Sale(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super().__setup__()
cls.warehouse.states['readonly'] = (
cls.warehouse.states.get('readonly', False)
| Eval('eu_excise_number'))
editable = ~Eval('eu_excise_number')
if cls.warehouse.states.get('editable'):
cls.warehouse.states['editable'] &= editable
else:
cls.warehouse.states['editable'] = editable
@fields.depends(methods=['_clear_eu_excise_number'])
def on_change_party(self):
@@ -448,9 +448,9 @@ class InvoiceLine(metaclass=PoolMeta):
__name__ = 'account.invoice.line'
shipment_cost = fields.Many2One(
'account.shipment_cost', "Shipment Cost",
readonly=True,
states={
'invisible': ~Eval('shipment_cost'),
'editable': False,
})
@classmethod
+5 -2
View File
@@ -13,8 +13,11 @@ class Purchase(metaclass=PoolMeta):
def __setup__(cls):
super().__setup__()
for field in (cls.invoice_address, cls.warehouse):
field.states['readonly'] |= (
Eval('lines', [0]) & Eval('invoice_address'))
editable = ~(Eval('lines', [0]) & Eval('invoice_address'))
if field.states.get('editable'):
field.states['editable'] &= editable
else:
field.states['editable'] = editable
class Line(metaclass=PoolMeta):
+5 -2
View File
@@ -13,8 +13,11 @@ class Sale(metaclass=PoolMeta):
def __setup__(cls):
super().__setup__()
for field in (cls.shipment_party, cls.shipment_address, cls.warehouse):
field.states['readonly'] |= (
Eval('lines', [0]) & Eval('shipment_address'))
editable = ~(Eval('lines', [0]) & Eval('shipment_address'))
if field.states.get('editable'):
field.states['editable'] &= editable
else:
field.states['editable'] = editable
class Line(metaclass=PoolMeta):
+1 -1
View File
@@ -53,7 +53,7 @@ class Budget(BudgetMixin, ModelSQL, ModelView):
('type', '=', 'root'),
],
states={
'readonly': Eval('root') & Eval('lines', [-1]),
'editable': ~(Eval('root') & Eval('lines', [-1])),
})
lines = fields.One2Many(
'analytic_account.budget.line', 'budget', "Lines",
+2 -2
View File
@@ -17,14 +17,14 @@ class Carrier(metaclass=PoolMeta):
states={
'invisible': Eval('carrier_cost_method') != 'weight',
'required': Eval('carrier_cost_method') == 'weight',
'readonly': Bool(Eval('weight_price_list', [])),
'editable': Bool(Eval('weight_price_list', [])),
},
help="The Unit of Measure of weight criteria for the price list.")
weight_currency = fields.Many2One('currency.currency', 'Currency',
states={
'invisible': Eval('carrier_cost_method') != 'weight',
'required': Eval('carrier_cost_method') == 'weight',
'readonly': Bool(Eval('weight_price_list', [])),
'editable': Bool(Eval('weight_price_list', [])),
},
help="The currency of the price.")
weight_price_list = fields.One2Many('carrier.weight_price_list', 'carrier',
+2 -1
View File
@@ -47,7 +47,8 @@ class Party(
code = fields.Char(
"Code", required=True,
states={
'readonly': Eval('code_readonly', True),
'readonly': Eval('id', -1) >= 0,
'editable': ~Eval('code_readonly', True),
},
help="The unique identifier of the party.")
code_readonly = fields.Function(fields.Boolean('Code Readonly'),
+2 -1
View File
@@ -196,7 +196,8 @@ class RelationAll(Relation, ModelView):
Relation = pool.get('party.relation')
vlist = cls._before_create(vlist)
relations = Relation.create(vlist)
return cls.browse(cls._after_create([r.id * 2 for r in relations]))
return cls.browse(cls._after_create(
[r.id * 2 for r in relations], vlist))
@classmethod
def write(cls, *args):
+4 -3
View File
@@ -153,7 +153,7 @@ class Template(
list_price = fields.MultiValue(fields.Numeric(
"List Price", digits=price_digits,
states={
'readonly': ~Eval('context', {}).get('company'),
'editable': ~Eval('context', {}).get('company'),
},
help="The standard price the product is sold at."))
list_prices = fields.One2Many(
@@ -515,7 +515,7 @@ class Product(
list_price = fields.MultiValue(fields.Numeric(
"List Price", digits=price_digits,
states={
'readonly': ~Eval('context', {}).get('company'),
'editable': ~Eval('context', {}).get('company'),
},
help="The standard price the variant is sold at.\n"
"Leave empty to use the list price of the product."))
@@ -528,7 +528,7 @@ class Product(
cost_price = fields.MultiValue(fields.Numeric(
"Cost Price", digits=price_digits,
states={
'readonly': ~Eval('context', {}).get('company'),
'editable': ~Eval('context', {}).get('company'),
},
help="The amount it costs to purchase or make the variant, "
"or carry out the service."))
@@ -633,6 +633,7 @@ class Product(
setattr(cls, 'order_%s' % attr, order_method)
if isinstance(tfield, fields.One2Many):
getattr(cls, attr).setter = '_set_template_function'
getattr(cls, attr)._field.readonly = False
@classmethod
def _set_template_function(cls, products, name, value):
+1 -1
View File
@@ -40,7 +40,7 @@ class Product(metaclass=PoolMeta):
readonly = (
Eval('context', {}).get('cost_price_warehouse', False)
& ~Eval('context', {}).get('warehouse'))
if cls.cost_price.states['readonly']:
if cls.cost_price.states.get('readonly'):
cls.cost_price.states['readonly'] |= readonly
else:
cls.cost_price.states['readonly'] = readonly
+14 -10
View File
@@ -47,16 +47,20 @@ class Production(
('type', '=', 'warehouse'),
],
states={
'readonly': (~Eval('state').in_(['request', 'draft'])
| Eval('inputs', [-1]) | Eval('outputs', [-1])),
'readonly': ~Eval('state').in_(['request', 'draft']),
'editable': ~(
Eval('inputs', [-1])
| Eval('outputs', [-1])),
})
location = fields.Many2One('stock.location', 'Location', required=True,
domain=[
('type', '=', 'production'),
],
states={
'readonly': (~Eval('state').in_(['request', 'draft'])
| Eval('inputs', [-1]) | Eval('outputs', [-1])),
'readonly': ~Eval('state').in_(['request', 'draft']),
'editable': ~(
Eval('inputs', [-1])
| Eval('outputs', [-1])),
})
type = fields.Selection([
('assembly', "Assembly"),
@@ -87,8 +91,8 @@ class Production(
),
],
states={
'readonly': (~Eval('state').in_(['request', 'draft'])
| ~Eval('warehouse', 0) | ~Eval('location', 0)),
'readonly': ~Eval('state').in_(['request', 'draft']),
'editable': Eval('warehouse', None) & Eval('location', None),
'invisible': ~Eval('product'),
})
uom_category = fields.Function(fields.Many2One(
@@ -123,8 +127,8 @@ class Production(
('company', '=', Eval('company', -1)),
],
states={
'readonly': (~Eval('state').in_(['request', 'draft', 'waiting'])
| ~Eval('warehouse') | ~Eval('location')),
'readonly': ~Eval('state').in_(['request', 'draft', 'waiting']),
'editable': Eval('warehouse', None) & Eval('location', None),
})
outputs = fields.One2Many(
'stock.move', 'production_output', "Output Materials",
@@ -138,8 +142,8 @@ class Production(
('company', '=', Eval('company', -1)),
],
states={
'readonly': (Eval('state').in_(['done', 'cancelled'])
| ~Eval('warehouse') | ~Eval('location')),
'readonly': Eval('state').in_(['done', 'cancelled']),
'editable': Eval('warehouse', None) & Eval('location', None),
})
assigned_by = employee_field("Assigned By")
+4 -2
View File
@@ -46,7 +46,7 @@ class Location(metaclass=PoolMeta):
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
production_input = fields.Many2One(
'production', "Production Input", readonly=True, ondelete='CASCADE',
'production', "Production Input", ondelete='CASCADE',
domain=[
('company', '=', Eval('company', -1)),
If(Eval('production_output', None),
@@ -55,9 +55,10 @@ class Move(metaclass=PoolMeta):
],
states={
'invisible': ~Eval('production_input'),
'readonly': Eval('id', -1) >= 0,
})
production_output = fields.Many2One(
'production', "Production Output", readonly=True, ondelete='CASCADE',
'production', "Production Output", ondelete='CASCADE',
domain=[
('company', '=', Eval('company', -1)),
If(Eval('production_input', None),
@@ -66,6 +67,7 @@ class Move(metaclass=PoolMeta):
],
states={
'invisible': ~Eval('production_output'),
'readonly': Eval('id', -1) >= 0,
})
production = fields.Function(fields.Many2One(
'production', "Production",
@@ -82,7 +82,9 @@ Change cost of input::
>>> Move = Model.get('stock.move')
>>> input, = production.inputs
>>> Move.write([input], {'cost_price': Decimal(6)}, config.context)
>>> Move.write(
... [input], {'cost_price': Decimal(6)},
... {**config.context, '_check_access': False})
>>> input.reload()
>>> bool(input.production_cost_price_updated)
True
+1 -1
View File
@@ -161,7 +161,7 @@ class Work(
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (
'editable': ~(
Eval('origin', None)
| Eval('parent', None)
| Eval('children', [])
+18 -19
View File
@@ -85,9 +85,9 @@ class Purchase(
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| Eval('lines', [0])
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('lines', [0])
| Eval('party', True)
| Eval('invoice_party', True)),
})
@@ -112,8 +112,8 @@ class Purchase(
})
party = fields.Many2One('party.party', 'Party', required=True,
states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('party'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('party')),
},
context={
'company': Eval('company', -1),
@@ -132,8 +132,8 @@ class Purchase(
depends={'company'})
invoice_party = fields.Many2One('party.party', "Invoice Party",
states={
'readonly': ((Eval('state') != 'draft')
| Eval('lines', [0])),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
context={
'company': Eval('company', -1),
@@ -157,16 +157,14 @@ class Purchase(
domain=[('type', '=', 'warehouse')], states=_states)
currency = fields.Many2One('currency.currency', 'Currency', required=True,
states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('currency')),
})
lines = fields.One2Many(
'purchase.line', 'purchase', "Lines",
states={
'readonly': (
(Eval('state') != 'draft')
| ~Eval('company')
| ~Eval('currency')),
'readonly': Eval('state') != 'draft',
'editable': Eval('company', None) & Eval('currency', None),
})
line_lines = fields.One2Many(
'purchase.line', 'purchase', "Line - Lines", readonly=True,
@@ -1177,8 +1175,8 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
purchase = fields.Many2One(
'purchase.purchase', "Purchase", ondelete='CASCADE', required=True,
states={
'readonly': ((Eval('purchase_state') != 'draft')
& Bool(Eval('purchase'))),
'readonly': Eval('purchase_state') != 'draft',
'editable': ~Eval('purchase'),
})
type = fields.Selection([
('line', 'Line'),
@@ -1403,9 +1401,9 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
delivery_date = fields.Function(fields.Date('Delivery Date',
states={
'invisible': Eval('type') != 'line',
'readonly': (Eval('purchase_state').in_(
['processing', 'done', 'cancelled'])
| ~Eval('delivery_date_edit', False)),
'readonly': Eval('purchase_state').in_(
['processing', 'done', 'cancelled']),
'editable': Eval('delivery_date_edit', False),
}),
'on_change_with_delivery_date', setter='set_delivery_date')
delivery_date_edit = fields.Boolean(
@@ -1425,7 +1423,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
},
help="Check to edit the delivery date.")
delivery_date_store = fields.Date(
"Delivery Date", readonly=True,
"Delivery Date",
domain=[
If(Eval('type') != 'line',
('delivery_date_store', '=', None),
@@ -1433,6 +1431,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
],
states={
'invisible': Eval('type') != 'line',
'editable': False
})
purchase_state = fields.Function(
fields.Selection('get_purchase_states', 'Purchase State'),
+4 -4
View File
@@ -42,7 +42,8 @@ class Amendment(Workflow, ModelSQL, ModelView):
('state', 'in', ['processing', 'done']),
],
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
date = fields.Date(
"Date", required=True,
@@ -248,9 +249,8 @@ class AmendmentLine(ModelSQL, ModelView):
('default_uom_category', '=', Eval('product_uom_category', -1)),
],
states={
'readonly': (
(Eval('state') != 'draft')
| ~Eval('product_uom_category', None)),
'readonly': Eval('state') != 'draft',
'editable': Bool(Eval('product_uom_category', None)),
'invisible': Eval('action') != 'line',
})
product_supplier = fields.Many2One(
+9 -12
View File
@@ -115,9 +115,9 @@ class BlanketAgreement(Workflow, ModelSQL, ModelView, ChatMixin):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| Eval('lines', [0])
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('lines', [0])
| Eval('supplier', True)),
})
number = fields.Char("Number", readonly=True)
@@ -130,9 +130,8 @@ class BlanketAgreement(Workflow, ModelSQL, ModelView, ChatMixin):
supplier = fields.Many2One(
'party.party', "Supplier", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('supplier'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('supplier', None)),
},
context={
'company': Eval('company', -1),
@@ -163,16 +162,14 @@ class BlanketAgreement(Workflow, ModelSQL, ModelView, ChatMixin):
currency = fields.Many2One(
'currency.currency', "Currency", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency', 0))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('currency', 0)),
})
lines = fields.One2Many(
'purchase.blanket_agreement.line', 'blanket_agreement', "Lines",
states={
'readonly': (
(Eval('state') != 'draft')
| ~Eval('supplier'))
'readonly': Eval('state') != 'draft',
'editable': Bool(Eval('supplier', None)),
})
amount = fields.Function(Monetary(
+1 -4
View File
@@ -10,10 +10,7 @@ class Purchase(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super().__setup__()
cls.party.states['readonly'] = (cls.party.states['readonly']
| Eval('lines', [0]))
cls.lines.states['readonly'] = (cls.lines.states['readonly']
| ~Eval('party'))
cls.lines.states['editable'] &= Eval('party', None)
class Line(metaclass=PoolMeta):
@@ -111,7 +111,7 @@ class Quotation(Workflow, ModelSQL, ModelView, ChatMixin):
supplier = fields.Many2One(
'party.party', "Supplier", required=True,
states={
'readonly': Eval('lines', [0]) & Eval('supplier'),
'editable': Eval('lines', [0]) & Eval('supplier'),
},
context={
'company': Eval('company', -1),
+4 -3
View File
@@ -78,7 +78,8 @@ class PurchaseRequisition(Workflow, ModelSQL, ModelView, ChatMixin):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
number = fields.Char('Number', readonly=True)
description = fields.Char('Description', states=_states)
@@ -99,8 +100,8 @@ class PurchaseRequisition(Workflow, ModelSQL, ModelView, ChatMixin):
currency = fields.Many2One(
'currency.currency', 'Currency',
states={
'readonly': (_states['readonly']
| (Eval('lines', [0]) & Eval('currency'))),
'readonly': _states['readonly'],
'editable': ~(Eval('lines', [0]) & Eval('currency')),
})
total_amount = fields.Function(
Monetary("Total", currency='currency', digits='currency'),
+4 -2
View File
@@ -43,14 +43,16 @@ class Line(metaclass=PoolMeta):
secondary_uom_factor = fields.Float(
"Secondary UoM Factor",
states={
'readonly': True,
'readonly': Eval('purchase_state') != 'draft',
'editable': False,
'required': (Eval('type') == 'line') & Eval('secondary_unit'),
},
help="The factor for the secondary Unit of Measure.")
secondary_uom_rate = fields.Float(
"Secondary UoM Rate",
states={
'readonly': True,
'readonly': Eval('purchase_state') != 'draft',
'editable': False,
'required': (Eval('type') == 'line') & Eval('secondary_unit'),
},
help="The rate for the secondary Unit of Measure.")
+25 -20
View File
@@ -81,9 +81,9 @@ class Sale(
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| Eval('lines', [0])
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('lines', [0])
| Eval('party', True)
| Eval('invoice_party', True)
| Eval('shipment_party', True)),
@@ -97,9 +97,8 @@ class Sale(
quotation_date = fields.Date(
"Quotation Date",
states={
'readonly': (
(Eval('state') != 'draft')
| (Eval('number') & Eval('lines'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('number') & Eval('lines', [0])),
'invisible': ~Eval('state').in_(['draft', 'quotation']),
},
help="When the quotation was edited.")
@@ -140,8 +139,8 @@ class Sale(
party = fields.Many2One(
'party.party', "Party", required=True,
states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('party'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('party')),
},
context={
'company': Eval('company', -1),
@@ -160,8 +159,8 @@ class Sale(
depends={'company'})
invoice_party = fields.Many2One('party.party', "Invoice Party",
states={
'readonly': ((Eval('state') != 'draft')
| Eval('lines', [0])),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
context={
'company': Eval('company', -1),
@@ -213,16 +212,14 @@ class Sale(
})
currency = fields.Many2One('currency.currency', 'Currency', required=True,
states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency', 0))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('currency', 0)),
})
lines = fields.One2Many(
'sale.line', 'sale', "Lines",
states={
'readonly': (
(Eval('state') != 'draft')
| ~Eval('company')
| ~Eval('currency')),
'readonly': Eval('state') != 'draft',
'editable': Eval('company', None) & Eval('currency', None),
})
line_lines = fields.One2Many(
'sale.line', 'sale', "Line - Lines", readonly=True,
@@ -556,6 +553,10 @@ class Sale(
def default_invoice_state():
return 'none'
@classmethod
def default_to_invoice(cls):
return False
@classmethod
def default_shipment_method(cls, **pattern):
Config = Pool().get('sale.configuration')
@@ -566,6 +567,10 @@ class Sale(
def default_shipment_state():
return 'none'
@classmethod
def default_to_ship(cls):
return False
@fields.depends(
'company', 'party', 'invoice_party', 'shipment_party', 'warehouse',
'payment_term', 'lines')
@@ -916,10 +921,10 @@ class Sale(
default.setdefault('number', None)
default.setdefault('reference')
default.setdefault('invoice_state', 'none')
default.setdefault('to_invoice')
default.setdefault('to_invoice', cls.default_to_invoice())
default.setdefault('invoices_ignored', None)
default.setdefault('shipment_state', 'none')
default.setdefault('to_ship')
default.setdefault('to_ship', cls.default_to_ship())
default.setdefault('quotation_date')
default.setdefault('sale_date', None)
default.setdefault('quoted_by')
@@ -1389,8 +1394,8 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
sale = fields.Many2One(
'sale.sale', "Sale", ondelete='CASCADE', required=True,
states={
'readonly': ((Eval('sale_state') != 'draft')
& Bool(Eval('sale'))),
'readonly': Eval('sale_state') != 'draft',
'editable': ~Eval('sale'),
})
type = fields.Selection([
('line', 'Line'),
@@ -46,8 +46,9 @@ Create a quotation::
Expire quotation::
>>> sale.quotation_date = today - dt.timedelta(weeks=2)
>>> sale.save()
>>> Sale.write([sale.id], {
... 'quotation_date': today - dt.timedelta(weeks=2)
... }, {**config._context, '_check_access': False})
>>> cron, = Cron.find(
... [('method', '=', 'sale.sale|cancel_expired_quotation')], limit=1)
+4 -4
View File
@@ -42,7 +42,8 @@ class Amendment(Workflow, ModelSQL, ModelView):
('state', 'in', ['processing', 'done']),
],
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
date = fields.Date(
"Date", required=True,
@@ -286,9 +287,8 @@ class AmendmentLine(ModelSQL, ModelView):
('default_uom_category', '=', Eval('product_uom_category', -1)),
],
states={
'readonly': (
(Eval('state') != 'draft')
| ~Eval('product_uom_category', None)),
'readonly': Eval('state') != 'draft',
'editable': Bool(Eval('product_uom_category', None)),
'invisible': Eval('action') != 'line',
})
quantity = fields.Float(
+9 -12
View File
@@ -115,9 +115,9 @@ class BlanketAgreement(Workflow, ModelSQL, ModelView, ChatMixin):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| Eval('lines', [0])
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('lines', [0])
| Eval('customer', True)),
})
number = fields.Char("Number", readonly=True)
@@ -130,9 +130,8 @@ class BlanketAgreement(Workflow, ModelSQL, ModelView, ChatMixin):
customer = fields.Many2One(
'party.party', "Customer", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('customer'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('customer', None)),
},
context={
'company': Eval('company', -1),
@@ -163,16 +162,14 @@ class BlanketAgreement(Workflow, ModelSQL, ModelView, ChatMixin):
currency = fields.Many2One(
'currency.currency', "Currency", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency', 0))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('currency', 0)),
})
lines = fields.One2Many(
'sale.blanket_agreement.line', 'blanket_agreement', "Lines",
states={
'readonly': (
(Eval('state') != 'draft')
| ~Eval('customer'))
'readonly': Eval('state') != 'draft',
'editable': Bool(Eval('customer', None)),
})
amount = fields.Function(Monetary(
+7 -5
View File
@@ -56,7 +56,8 @@ class Complaint(Workflow, ModelSQL, ModelView, ChatMixin):
company = fields.Many2One(
'company.company', 'Company', required=True,
states={
'readonly': _states['readonly'] | Eval('origin'),
'readonly': _states['readonly'],
'editable': ~Eval('origin'),
})
type = fields.Many2One('sale.complaint.type', 'Type', required=True,
states=_states)
@@ -96,8 +97,8 @@ class Complaint(Workflow, ModelSQL, ModelView, ChatMixin):
],
},
states={
'readonly': ((Eval('state') != 'draft')
| Bool(Eval('actions', [0]))),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('actions', [0]),
'required': Bool(Eval('origin_model')),
},
depends={'origin_model'})
@@ -108,8 +109,9 @@ class Complaint(Workflow, ModelSQL, ModelView, ChatMixin):
description = fields.Text('Description', states=_states)
actions = fields.One2Many('sale.complaint.action', 'complaint', 'Actions',
states={
'readonly': ((Eval('state') != 'draft')
| (If(~Eval('origin_id', 0), 0, Eval('origin_id', 0)) <= 0)),
'readonly': Eval('state') != 'draft',
'editable': (
If(~Eval('origin_id', 0), -1, Eval('origin_id', -1)) < 0),
},
depends={'origin_model'})
submitted_by = employee_field(
+1 -1
View File
@@ -71,7 +71,7 @@ class Extra(DeactivableMixin, ModelSQL, ModelView, MatchMixin):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': Eval('id', 0) > 0,
'readonly': Eval('id', -1) >= 0,
})
start_date = fields.Date('Start Date',
domain=['OR',
+13 -4
View File
@@ -11,7 +11,7 @@ from trytond.model import Exclude, ModelSQL, ModelView, Workflow, fields
from trytond.modules.company.model import CompanyValueMixin
from trytond.modules.currency.fields import Monetary
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, Id
from trytond.pyson import Eval, Id
from trytond.report import Report, get_email
from trytond.sendmail import send_message_transactional
from trytond.tools.email_ import (
@@ -58,7 +58,9 @@ class GiftCard(ModelSQL, ModelView):
_rec_name = 'number'
_states = {
'readonly': Bool(Eval('origin')) | Bool(Eval('spent_on')),
'readonly': (
(Eval('origin', None) | Eval('spent_on', None))
& (Eval('id', -1) >= 0)),
}
number = fields.Char(
@@ -80,9 +82,16 @@ class GiftCard(ModelSQL, ModelView):
currency = fields.Many2One(
'currency.currency', "Currency", required=True, states=_states)
origin = fields.Reference("Origin", selection='get_origin', readonly=True)
origin = fields.Reference(
"Origin", selection='get_origin',
states={
'editable': Eval('id', -1) >= 0,
})
spent_on = fields.Reference(
"Spent On", selection='get_spent_on', readonly=True)
"Spent On", selection='get_spent_on',
states={
'editable': Eval('id', -1) >= 0,
})
del _states
+2 -1
View File
@@ -65,7 +65,8 @@ class SaleOpportunity(
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': _states_stop['readonly'] | Eval('party', True),
'readonly': _states_stop['readonly'],
'editable': ~Eval('party', True),
},
domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
+4 -5
View File
@@ -111,9 +111,8 @@ class POSSale(Workflow, ModelSQL, ModelView, TaxableMixin, ChatMixin):
point = fields.Many2One(
'sale.point', "Point", required=True, ondelete='RESTRICT',
states={
'readonly': ((Eval('id', 0) > 0)
| Bool(Eval('lines', [0]))
| _states['readonly']),
'readonly': (Eval('id', -1) >= 0) | _states['readonly'],
'editable': ~Eval('lines', [0]),
},
domain=[
('company', '=', Eval('company', -1)),
@@ -482,12 +481,12 @@ class POSSaleLine(ModelSQL, ModelView, TaxableMixin):
unit_list_price = fields.Numeric(
"Unit List Price", digits=price_digits, required=True,
states={
'readonly': True, # Allow client to sent value
'editable': False,
})
unit_gross_price = fields.Numeric(
"Unit Gross Price", digits=price_digits, required=True,
states={
'readonly': True, # Allow client to sent value
'editable': False,
})
unit_price = fields.Function(fields.Numeric(
"Unit Price", digits=price_digits,
+4 -7
View File
@@ -2,7 +2,7 @@
# this repository contains the full copyright notices and license terms.
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Equal, Eval, Not, Or
from trytond.pyson import Eval
class Sale(metaclass=PoolMeta):
@@ -11,17 +11,14 @@ class Sale(metaclass=PoolMeta):
help="Use to compute the unit price of lines.",
domain=[('company', '=', Eval('company', -1))],
states={
'readonly': Or(Not(Equal(Eval('state'), 'draft')),
Bool(Eval('lines', [0]))),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
@classmethod
def __setup__(cls):
super().__setup__()
cls.party.states['readonly'] = (cls.party.states['readonly']
| Eval('lines', [0]))
cls.lines.states['readonly'] = (cls.lines.states['readonly']
| ~Eval('party') | ~Eval('company'))
cls.lines.states['editable'] &= Eval('party', None)
@fields.depends('company')
def on_change_party(self):
+1 -1
View File
@@ -184,7 +184,7 @@ class Promotion(
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': Eval('id', 0) > 0,
'readonly': Eval('id', -1) >= 0,
})
start_date = fields.Date('Start Date',
domain=['OR',
+8 -8
View File
@@ -130,7 +130,8 @@ class Rental(
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (Eval('state') != 'draft') | Eval('party', True),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('party', True),
},
help="Make the sale rental belong to the company.")
@@ -149,9 +150,8 @@ class Rental(
party = fields.Many2One(
'party.party', "Party", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('party'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('party')),
},
context={
'company': Eval('company', -1),
@@ -169,8 +169,8 @@ class Rental(
depends={'company', 'party'})
invoice_party = fields.Many2One('party.party', "Invoice Party",
states={
'readonly': ((Eval('state') != 'draft')
| Eval('lines', [0])),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
context={
'company': Eval('company', -1),
@@ -207,8 +207,8 @@ class Rental(
currency = fields.Many2One(
'currency.currency', "Currency", required=True,
states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency', 0))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('currency', None)),
})
start = fields.Function(
+4 -2
View File
@@ -43,14 +43,16 @@ class Line(metaclass=PoolMeta):
secondary_uom_factor = fields.Float(
"Secondary UoM Factor",
states={
'readonly': True,
'readonly': Eval('sale_state') != 'draft',
'editable': False,
'required': (Eval('type') == 'line') & Eval('secondary_unit'),
},
help="The factor for the secondary Unit of Measure.")
secondary_uom_rate = fields.Float(
"Secondary UoM Rate",
states={
'readonly': True,
'readonly': Eval('sale_state') != 'draft',
'editable': False,
'required': (Eval('type') == 'line') & Eval('secondary_unit'),
},
help="The rate for the secondary Unit of Measure.")
+4 -6
View File
@@ -17,9 +17,8 @@ class ShipmentCostSaleMixin:
'currency.currency', "Cost Sale Currency",
states={
'invisible': Eval('cost_sale_method') != 'shipment',
'readonly': (
Eval('shipment_cost_sale_readonly', True)
| ~Eval('cost_edit', False)),
'readonly': Eval('shipment_cost_sale_readonly', True),
'editable': Eval('cost_edit', False),
}),
'on_change_with_cost_sale_currency_used', setter='set_cost')
cost_sale_currency = fields.Many2One(
@@ -33,9 +32,8 @@ class ShipmentCostSaleMixin:
"Cost Sale", digits=price_digits,
states={
'invisible': Eval('cost_sale_method') != 'shipment',
'readonly': (
Eval('shipment_cost_sale_readonly', True)
| ~Eval('cost_edit', False)),
'readonly': Eval('shipment_cost_sale_readonly', True),
'editable': Eval('cost_edit', False),
}),
'on_change_with_cost_sale_used', setter='set_cost')
cost_sale = fields.Numeric(
+9 -9
View File
@@ -32,9 +32,9 @@ class Subscription(Workflow, ModelSQL, ModelView, ChatMixin):
company = fields.Many2One(
'company.company', "Company", required=True,
states={
'readonly': (
(Eval('state') != 'draft')
| Eval('lines', [0])
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('lines', [0])
| Eval('party', True)
| Eval('invoice_party', True)),
},
@@ -54,8 +54,8 @@ class Subscription(Workflow, ModelSQL, ModelView, ChatMixin):
party = fields.Many2One(
'party.party', "Party", required=True,
states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('party'))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('party', None)),
},
context={
'company': Eval('company', -1),
@@ -73,8 +73,8 @@ class Subscription(Workflow, ModelSQL, ModelView, ChatMixin):
depends={'company', 'party'})
invoice_party = fields.Many2One('party.party', "Invoice Party",
states={
'readonly': ((Eval('state') != 'draft')
| Eval('lines', [0])),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
context={
'company': Eval('company', -1),
@@ -102,8 +102,8 @@ class Subscription(Workflow, ModelSQL, ModelView, ChatMixin):
currency = fields.Many2One(
'currency.currency', "Currency", required=True,
states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency', 0))),
'readonly': Eval('state') != 'draft',
'editable': ~(Eval('lines', [0]) & Eval('currency', 0)),
})
start_date = fields.Date(
+8 -4
View File
@@ -34,12 +34,15 @@ class Inventory(Workflow, ModelSQL, ModelView, ChatMixin):
help="The main identifier for the inventory.")
location = fields.Many2One(
'stock.location', 'Location', required=True,
domain=[('type', '=', 'storage')], states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
domain=[('type', '=', 'storage')],
states={
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
help="The location inventoried.")
date = fields.Date('Date', required=True, states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
help="The date of the stock count.")
lines = fields.One2Many(
@@ -56,7 +59,8 @@ class Inventory(Workflow, ModelSQL, ModelView, ChatMixin):
help="How lines without a quantity are handled.")
company = fields.Many2One('company.company', 'Company', required=True,
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
},
help="The company the inventory is associated with.")
state = fields.Selection([
+23 -20
View File
@@ -18,7 +18,7 @@ from trytond.model import (
from trytond.model.exceptions import AccessError
from trytond.modules.product import price_digits, round_price
from trytond.pool import Pool
from trytond.pyson import Bool, Equal, Eval, Id, If, TimeDelta
from trytond.pyson import Bool, Eval, Id, If, TimeDelta
from trytond.tools import cached_property
from trytond.transaction import Transaction, without_check_access
@@ -214,8 +214,8 @@ class Move(Workflow, ModelSQL, ModelView):
unit = fields.Many2One(
'product.uom', "Unit", required=True,
states={
'readonly': (Eval('state').in_(['cancelled', 'assigned', 'done'])
| Eval('unit_price')),
'readonly': Eval('state').in_(['cancelled', 'assigned', 'done']),
'editable': ~Eval('unit_price'),
},
domain=[
If(~Eval('state').in_(['done', 'cancelled']),
@@ -239,10 +239,10 @@ class Move(Workflow, ModelSQL, ModelView):
'stock.location', "From Location",
required=True,
states={
'readonly': (
STATES['readonly']
| (Eval('shipment')
& Equal(Eval('_parent_shipment.id', None), None))),
'readonly': STATES['readonly'],
'editable': ~(
Eval('shipment')
| Eval('_parent_shipment', None)),
},
domain=[
LOCATION_DOMAIN,
@@ -264,10 +264,10 @@ class Move(Workflow, ModelSQL, ModelView):
'stock.location', "To Location",
required=True,
states={
'readonly': (
STATES['readonly']
| (Eval('shipment')
& Equal(Eval('_parent_shipment.id', None), None))),
'readonly': STATES['readonly'],
'editable': ~(
Eval('shipment')
| Eval('_parent_shipment', None)),
},
domain=[
LOCATION_DOMAIN,
@@ -286,9 +286,10 @@ class Move(Workflow, ModelSQL, ModelView):
"To Location"),
'on_change_with_to_location_name')
shipment = fields.Reference(
"Shipment", selection='get_shipment', readonly=True,
"Shipment", selection='get_shipment',
states={
'invisible': ~Eval('shipment'),
'readonly': Eval('id', -1) >= 0,
},
help="Used to group several stock moves together.")
origin = fields.Reference(
@@ -305,20 +306,20 @@ class Move(Workflow, ModelSQL, ModelView):
planned_date = fields.Date(
"Planned Date",
states={
'readonly': (
Eval('state').in_(['cancelled', 'assigned', 'done'])
| Eval('shipment')
| Eval('_parent_shipment'))
'readonly': Eval('state').in_(['cancelled', 'assigned', 'done']),
'editable': ~(
Eval('shipment', None)
| Eval('_parent_shipment', None)),
},
help="When the stock is expected to be moved.")
effective_date = fields.Date(
"Effective Date",
states={
'required': Eval('state') == 'done',
'readonly': (
Eval('state').in_(['cancelled', 'done'])
| Eval('shipment')
| Eval('_parent_shipment')),
'readonly': Eval('state').in_(['cancelled', 'done']),
'editable': ~(
Eval('shipment', None)
| Eval('_parent_shipment', None)),
},
help="When the stock was actually moved.")
delay = fields.Function(
@@ -1024,6 +1025,8 @@ class Move(Workflow, ModelSQL, ModelView):
@classmethod
def copy(cls, moves, default=None):
default = default.copy() if default is not None else {}
default.setdefault('internal_quantity', 0)
default.setdefault('cost_price')
default.setdefault('outcome_moves', None)
return super().copy(moves, default=default)
+50 -31
View File
@@ -359,9 +359,10 @@ class ShipmentIn(
help="The company the shipment is associated with.")
supplier = fields.Many2One('party.party', 'Supplier',
states={
'readonly': (((Eval('state') != 'draft')
| Eval('incoming_moves', [0]))
& Eval('supplier')),
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('incoming_moves', [0])
& Eval('supplier', None)),
}, required=True,
context={
'company': Eval('company', -1),
@@ -381,8 +382,10 @@ class ShipmentIn(
warehouse = fields.Many2One('stock.location', "Warehouse",
required=True, domain=[('type', '=', 'warehouse')],
states={
'readonly': (Eval('state').in_(['cancelled', 'done'])
| Eval('incoming_moves', [0]) | Eval('inventory_moves', [0])),
'readonly': Eval('state').in_(['cancelled', 'done']),
'editable': ~(
Eval('incoming_moves', [0])
| Eval('inventory_moves', [0])),
},
help="Where the stock is received.")
warehouse_input = fields.Many2One(
@@ -822,10 +825,12 @@ class ShipmentInReturn(
help="The company the shipment is associated with.")
supplier = fields.Many2One('party.party', 'Supplier',
states={
'readonly': (((Eval('state') != 'draft')
| Eval('moves', [0]))
& Eval('supplier', 0)),
}, required=True,
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('moves', [0])
& Eval('supplier', None)),
},
required=True,
context={
'company': Eval('company', -1),
'party_contact_mechanism_usage': 'delivery',
@@ -847,14 +852,20 @@ class ShipmentInReturn(
],
help="Where the stock is sent to.")
from_location = fields.Many2One('stock.location', "From Location",
required=True, states={
'readonly': (Eval('state') != 'draft') | Eval('moves', [0]),
}, domain=[('type', 'in', ['storage', 'view'])],
required=True,
states={
'readonly': Eval('state') != 'draft',
'editable': ~Eval('moves', [0]),
},
domain=[('type', 'in', ['storage', 'view'])],
help="Where the stock is moved from.")
to_location = fields.Many2One('stock.location', "To Location",
required=True, states={
'readonly': (Eval('state') != 'draft') | Eval('moves', [0]),
}, domain=[('type', '=', 'supplier')],
required=True,
states={
'readonly': Eval('state') != 'draft',
'editable': ~Eval('moves', [0]),
},
domain=[('type', '=', 'supplier')],
help="Where the stock is moved to.")
warehouse = fields.Function(
fields.Many2One('stock.location', "Warehouse"),
@@ -1162,8 +1173,8 @@ class ShipmentOut(
help="The company the shipment is associated with.")
customer = fields.Many2One('party.party', 'Customer', required=True,
states={
'readonly': ((Eval('state') != 'draft')
| Eval('outgoing_moves', [0])),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('outgoing_moves', [0]),
},
context={
'company': Eval('company', -1),
@@ -1190,9 +1201,12 @@ class ShipmentOut(
help="Where the stock is sent to.")
warehouse = fields.Many2One('stock.location', "Warehouse", required=True,
states={
'readonly': ((Eval('state') != 'draft')
| Eval('outgoing_moves', [0]) | Eval('inventory_moves', [0])),
}, domain=[('type', '=', 'warehouse')],
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('outgoing_moves', [0])
| Eval('inventory_moves', [0])),
},
domain=[('type', '=', 'warehouse')],
help="Where the stock is sent from.")
warehouse_storage = fields.Many2One(
'stock.location', "Warehouse Storage", required=True,
@@ -1876,8 +1890,8 @@ class ShipmentOutReturn(
help="The company the shipment is associated with.")
customer = fields.Many2One('party.party', 'Customer', required=True,
states={
'readonly': ((Eval('state') != 'draft')
| Eval('incoming_moves', [0])),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('incoming_moves', [0]),
},
context={
'company': Eval('company', -1),
@@ -1896,9 +1910,12 @@ class ShipmentOutReturn(
help="The address the customer can be contacted at.")
warehouse = fields.Many2One('stock.location', "Warehouse", required=True,
states={
'readonly': ((Eval('state') != 'draft')
| Eval('incoming_moves', [0]) | Eval('inventory_moves', [0])),
}, domain=[('type', '=', 'warehouse')],
'readonly': Eval('state') != 'draft',
'editable': ~(
Eval('incoming_moves', [0])
| Eval('inventory_moves', [0])),
},
domain=[('type', '=', 'warehouse')],
help="Where the stock is returned.")
warehouse_storage = fields.Many2One(
'stock.location', "Warehouse Storage", required=True,
@@ -2337,18 +2354,20 @@ class ShipmentInternal(
help="The company the shipment is associated with.")
from_location = fields.Many2One('stock.location', "From Location",
required=True, states={
'readonly': (~Eval('state').in_(['request', 'draft'])
| Eval('moves', [0])),
'readonly': ~Eval('state').in_(['request', 'draft']),
'editable': ~Eval('moves', [0]),
},
domain=[
('type', 'in', ['view', 'storage', 'lost_found']),
],
help="Where the stock is moved from.")
to_location = fields.Many2One('stock.location', "To Location",
required=True, states={
'readonly': (~Eval('state').in_(['request', 'draft'])
| Eval('moves', [0])),
}, domain=[
required=True,
states={
'readonly': ~Eval('state').in_(['request', 'draft']),
'editable': ~Eval('moves', [0]),
},
domain=[
('type', 'in', ['view', 'storage', 'lost_found']),
],
help="Where the stock is moved to.")
@@ -107,7 +107,10 @@ Reopen the period::
Create an assigned move::
>>> Move.write([move], {'state': 'assigned'}, config._context)
>>> Move.write(
... [move], {'state': 'assigned'},
... {**config._context, '_check_access': False})
>>> move.reload()
>>> move.state
'assigned'
@@ -10,7 +10,7 @@ Imports::
>>> from proteus import Model, Wizard
>>> from trytond.modules.company.tests.tools import create_company, get_company
>>> from trytond.modules.stock.move import Move
>>> from trytond.modules.stock.move import Move as StockMove
>>> from trytond.tests.tools import activate_modules, assertEqual, assertFalse
>>> today = dt.date.today()
@@ -18,7 +18,7 @@ Imports::
Patch on_change_with_assignation_required::
>>> _ = patch.object(
... Move, 'on_change_with_assignation_required',
... StockMove, 'on_change_with_assignation_required',
... return_value=False).start()
Activate modules::
@@ -29,6 +29,8 @@ Get company::
>>> company = get_company()
>>> Move = Model.get('stock.move')
Create product::
>>> ProductUom = Model.get('product.uom')
@@ -129,8 +131,10 @@ Update unit price of a move::
... ])
>>> bool(move.unit_price_updated)
False
>>> move.unit_price = Decimal('130')
>>> move.save()
>>> Move.write(
... [move], {'unit_price': Decimal('130')},
... {**config._context, '_check_access': False})
>>> move.reload()
>>> bool(move.unit_price_updated)
True
@@ -151,8 +155,10 @@ Update unit price of a move::
Launch cron task::
>>> move.unit_price = Decimal('120')
>>> move.save()
>>> Move.write(
... [move], {'unit_price': Decimal('120')},
... {**config._context, '_check_access': False})
>>> move.reload()
>>> Cron = Model.get('ir.cron')
>>> Company = Model.get('company.company')
+4 -2
View File
@@ -32,7 +32,8 @@ class Forecast(Workflow, ModelSQL, ModelView, ChatMixin):
warehouse = fields.Many2One(
'stock.location', 'Location', required=True,
domain=[('type', '=', 'warehouse')], states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
destination = fields.Many2One(
'stock.location', 'Destination', required=True,
@@ -49,7 +50,8 @@ class Forecast(Workflow, ModelSQL, ModelView, ChatMixin):
'stock.forecast.line', 'forecast', 'Lines', states=_states)
company = fields.Many2One(
'company.company', 'Company', required=True, states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
'readonly': Eval('state') != 'draft',
'editable': ~Eval('lines', [0]),
})
state = fields.Selection([
('draft', "Draft"),
+10 -4
View File
@@ -70,7 +70,7 @@ class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
from_place = fields.Many2One(
'stock.product.location.place', "From Place", readonly=True,
'stock.product.location.place', "From Place",
domain=[
If(~Eval('state').in_(['done', 'cancelled']),
['OR',
@@ -79,9 +79,12 @@ class Move(metaclass=PoolMeta):
],
('location', '=', Eval('from_location', -1)),
),
])
],
states={
'editable': False,
})
to_place = fields.Many2One(
'stock.product.location.place', "To Place", readonly=True,
'stock.product.location.place', "To Place",
domain=[
If(~Eval('state').in_(['done', 'cancelled']),
['OR',
@@ -90,7 +93,10 @@ class Move(metaclass=PoolMeta):
],
('location', '=', Eval('to_location', -1)),
),
])
],
states={
'editable': False,
})
@classmethod
def __setup__(cls):
@@ -93,7 +93,8 @@ Request to ship to first warehouse in 2 and 3 weeks::
>>> move.quantity = 10
>>> shipment_int1.save()
>>> ShipmentInternal.write(
... [shipment_int1.id], {'state': 'request'}, shipment_int1._context)
... [shipment_int1.id], {'state': 'request'},
... {**shipment_int1._context, '_check_access': False})
>>> shipment_int2 = ShipmentInternal()
>>> shipment_int2.planned_date = week2
@@ -106,7 +107,8 @@ Request to ship to first warehouse in 2 and 3 weeks::
>>> move.quantity = 5
>>> shipment_int2.save()
>>> ShipmentInternal.write(
... [shipment_int2.id], {'state': 'request'}, shipment_int2._context)
... [shipment_int2.id], {'state': 'request'},
... {**shipment_int2._context, '_check_access': False})
Plan to ship in 3 weeks::
+19 -7
View File
@@ -26,6 +26,16 @@ class Move(metaclass=PoolMeta):
super().__setup__()
cls._allow_modify_closed_period.add('shipment_out_cost_price')
@classmethod
def default_shipment_out_cost_price(cls):
return None
@classmethod
def copy(cls, moves, default=None):
default = default.copy() if default is not None else {}
default.setdefault('shipment_out_cost_price')
return super().copy(moves, default=default)
class ShipmentCostMixin:
__slots__ = ()
@@ -43,9 +53,8 @@ class ShipmentCostMixin:
cost_currency_used = fields.Function(fields.Many2One(
'currency.currency', "Cost Currency",
states={
'readonly': (
Eval('shipment_cost_readonly', True)
| ~Eval('cost_edit', False)),
'readonly': Eval('shipment_cost_readonly', True),
'editable': Eval('cost_edit', False),
}),
'on_change_with_cost_currency_used', setter='set_cost')
cost_currency = fields.Many2One(
@@ -58,12 +67,15 @@ class ShipmentCostMixin:
cost_used = fields.Function(fields.Numeric(
"Cost", digits=price_digits,
states={
'readonly': (
Eval('shipment_cost_readonly', True)
| ~Eval('cost_edit', False)),
'readonly': Eval('shipment_cost_readonly', True),
'editable': Eval('cost_edit', False),
}),
'on_change_with_cost_used', setter='set_cost')
cost = fields.Numeric("Cost", digits=price_digits, readonly=True)
cost = fields.Numeric(
"Cost", digits=price_digits,
states={
'editable': False,
})
cost_edit = fields.Boolean(
"Edit Cost",
states={
@@ -83,6 +83,14 @@ class Move(metaclass=PoolMeta):
"Internal Volume", readonly=True,
help="The volume of the moved product in liter.")
@classmethod
def default_internal_weight(cls):
return None
@classmethod
def default_internal_volume(cls):
return None
@fields.depends(
'quantity', 'unit', 'product',
methods=['on_change_with_internal_quantity'])
@@ -147,6 +155,13 @@ class Move(metaclass=PoolMeta):
values['internal_volume'] = internal_volume
return values
@classmethod
def copy(cls, moves, default=None):
default = default.copy() if default is not None else {}
default.setdefault('internal_weight', cls.default_internal_weight())
default.setdefault('internal_volume', cls.default_internal_volume())
return super().copy(moves, default=default)
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@@ -229,6 +244,14 @@ class MeasurementsMixin(object):
if set_measurements:
cls.set_measurements()
@classmethod
def default_internal_weight(cls):
return None
@classmethod
def default_internal_volume(cls):
return None
@classmethod
def _measurements_states(cls):
return []
@@ -433,6 +456,13 @@ class MeasurementsMixin(object):
def _measurements_location_condition(cls, table, move, location):
raise NotImplementedError
@classmethod
def copy(cls, records, default=None):
default = default.copy() if default is not None else {}
default.setdefault('internal_weight', cls.default_internal_weight())
default.setdefault('internal_volume', cls.default_internal_volume())
return super().copy(records, default=default)
def set_measurements(func):
@wraps(func)
+5 -5
View File
@@ -36,7 +36,7 @@ def str2bigint(s):
class Cron(DeactivableMixin, ModelSQL, ModelView):
__name__ = "ir.cron"
_states = {
'readonly': Eval('running', False),
'editable': Eval('running', False),
}
interval_number = fields.Integer(
"Interval Number", required=True, states=_states)
@@ -53,7 +53,7 @@ class Cron(DeactivableMixin, ModelSQL, ModelView):
[('minute', '>=', 0), ('minute', '<=', 59)],
],
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': Eval('interval_type').in_(['minutes']),
},
depends=['interval_type'])
@@ -63,14 +63,14 @@ class Cron(DeactivableMixin, ModelSQL, ModelView):
[('hour', '>=', 0), ('hour', '<=', 23)],
],
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': Eval('interval_type').in_(['minutes', 'hours']),
},
depends=['interval_type'])
weekday = fields.Many2One(
'ir.calendar.day', "Day of Week",
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': Eval('interval_type').in_(
['minutes', 'hours', 'days']),
},
@@ -81,7 +81,7 @@ class Cron(DeactivableMixin, ModelSQL, ModelView):
('day', '>=', 0),
],
states={
'readonly': _states['readonly'],
'editable': _states['editable'],
'invisible': Eval('interval_type').in_(
['minutes', 'hours', 'days', 'weeks']),
},
+4 -1
View File
@@ -564,7 +564,10 @@ class ModelStorage(Model):
def test_readonly(records, field_name, override):
field = cls._fields[field_name]
if isinstance(field, (fields.One2Many, fields.Many2Many)):
if (isinstance(field, (fields.One2Many, fields.Many2Many))
or (isinstance(field, fields.Function)
and isinstance(
field._field, (fields.One2Many, fields.Many2Many)))):
# Must be enforced on the target
return
if field.readonly: