Files
Cédric Krier 0cc22fcd25 Use default value when copying "updated" fields of stock move
Since 6c611c520744, the readonly of field is enforced so the copied value of
boolean field must be the default value for readonly fields.

Closes #15148
2026-10-05 18:03:10 +02:00

255 lines
9.1 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 sql import Cast, Null
from sql.conditionals import Case
from sql.operators import Concat
from trytond.model import Check, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, If
class Location(metaclass=PoolMeta):
__name__ = 'stock.location'
production_location = fields.Many2One('stock.location', 'Production',
states={
'invisible': Eval('type') != 'warehouse',
'required': Eval('type') == 'warehouse',
},
domain=[
('type', '=', 'production'),
])
production_picking_location = fields.Many2One(
'stock.location', "Production Picking",
states={
'invisible': Eval('type') != 'warehouse',
},
domain=[
('type', '=', 'storage'),
('parent', 'child_of', [Eval('id', -1)]),
],
help="Where the production components are picked from.\n"
"Leave empty to use the warehouse storage location.")
production_output_location = fields.Many2One(
'stock.location', "Production Output",
states={
'invisible': Eval('type') != 'warehouse',
},
domain=[
('type', '=', 'storage'),
('parent', 'child_of', [Eval('id', -1)]),
],
help="Where the produced goods are stored.\n"
"Leave empty to use the warehouse storage location.")
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
production_input = fields.Many2One(
'production', "Production Input", ondelete='CASCADE',
domain=[
('company', '=', Eval('company', -1)),
If(Eval('production_output', None),
('id', '=', None),
()),
],
states={
'invisible': ~Eval('production_input'),
'readonly': Eval('id', -1) >= 0,
})
production_output = fields.Many2One(
'production', "Production Output", ondelete='CASCADE',
domain=[
('company', '=', Eval('company', -1)),
If(Eval('production_input', None),
('id', '=', None),
()),
],
states={
'invisible': ~Eval('production_output'),
'readonly': Eval('id', -1) >= 0,
})
production = fields.Function(fields.Many2One(
'production', "Production",
states={
'invisible': ~Eval('production'),
},
depends=['production_input', 'production_output']),
'on_change_with_production', searcher='search_production')
production_cost_price_updated = fields.Boolean(
"Production Cost Price Updated", readonly=True,
states={
'invisible': ~Eval('production_input') & (Eval('state') == 'done'),
})
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints += [
('production_single', Check(t, (
(t.production_input == Null)
| (t.production_output == Null))),
'production.msg_stock_move_production_single'),
]
cls._allow_modify_closed_period.add('production_cost_price_updated')
@fields.depends(
'production_input',
'_parent_production_input.warehouse',
'_parent_production_input.location',
'from_location', 'to_location')
def on_change_production_input(self):
if self.production_input:
if not self.from_location:
self.from_location = self.production_input.picking_location
if not self.to_location:
self.to_location = self.production_input.location
@fields.depends(
'production_output',
'_parent_production_output.company',
'_parent_production_output.warehouse',
'_parent_production_output.location',
'from_location', 'to_location')
def on_change_production_output(self):
if self.production_output:
if not self.from_location:
self.from_location = self.production_output.location
if not self.to_location:
self.to_location = self.production_output.output_location
if self.production_output.company:
self.currency = self.production_output.company.currency
if self.to_location and self.to_location.type == 'lost_found':
self.currency = None
@fields.depends(
'production_output', '_parent_production_output.id', 'to_location')
def on_change_to_location(self):
try:
super().on_change_to_location()
except AttributeError:
pass
if (self.production_output
and self.to_location
and self.to_location.type == 'lost_found'):
self.currency = None
@fields.depends(
'production_input', '_parent_production_input.id',
'production_output', '_parent_production_output.id')
def on_change_with_production(self, name=None):
if self.production_input and self.production_input.id >= 0:
return self.production_input
elif self.production_output and self.production_output.id >= 0:
return self.production_output
@classmethod
def search_production(cls, name, clause):
_, operator, operand, *extra = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
nested = clause[0][len(name):]
return [bool_op,
('production_input' + nested, operator, operand, *extra),
('production_output' + nested, operator, operand, *extra),
]
@classmethod
def default_production_cost_price_updated(cls):
return False
def set_effective_date(self):
if not self.effective_date and self.production_input:
self.effective_date = self.production_input.effective_start_date
if not self.effective_date and self.production_output:
self.effective_date = self.production_output.effective_date
super().set_effective_date()
@classmethod
def on_modification(cls, mode, moves, field_names=None):
super().on_modification(mode, moves, field_names=field_names)
if mode == 'write' and 'cost_price' in field_names:
cls.write(
[m for m in moves if m.state == 'done' and m.production_input],
{'production_cost_price_updated': True})
def _rec_name_origin(self):
return super()._rec_name_origin() or self.production
@classmethod
def copy(cls, moves, default=None):
default = default.copy() if default is not None else {}
default.setdefault(
'production_cost_price_updated',
cls.default_production_cost_price_updated())
return super().copy(moves, default=default)
class ProductQuantitiesByWarehouseMove(metaclass=PoolMeta):
__name__ = 'stock.product_quantities_warehouse.move'
@classmethod
def _get_document_models(cls):
return super()._get_document_models() + ['production']
def get_document(self, name):
document = super().get_document(name)
if self.move.production_input:
document = str(self.move.production_input)
if self.move.production_output:
document = str(self.move.production_output)
return document
class LotTrace(metaclass=PoolMeta):
__name__ = 'stock.lot.trace'
production_input = fields.Many2One('production', "Production Input")
production_output = fields.Many2One('production', "Production Output")
@classmethod
def _columns(cls, tables):
move = tables['move']
return super()._columns(tables) + [
move.production_input.as_('production_input'),
move.production_output.as_('production_output'),
]
@classmethod
def get_documents(cls):
pool = Pool()
Model = pool.get('ir.model')
return super().get_documents() + [
('production', Model.get_name('production'))]
@classmethod
def get_document(cls, tables):
document = super().get_document(tables)
sql_type = cls.document.sql_type().base
move = tables['move']
return Case(
((move.production_input != Null),
Concat('production,',
Cast(move.production_input, sql_type))),
((move.production_output != Null),
Concat('production,',
Cast(move.production_output, sql_type))),
else_=document)
def _get_upward_traces(self):
if self.production_input:
traces = set(self.production_input.outputs)
else:
traces = super()._get_upward_traces()
return traces
def _get_downward_traces(self):
if self.production_output:
traces = set(self.production_output.inputs)
else:
traces = super()._get_downward_traces()
return traces