[IMP] repair: convert repair uom and location onchanges to compute
This allows to create a repair.order record without the need to call the onchanges to set the uom and locations or to set them manually during the `create` call. For instance, this makes easier to create repair orders using XMLRPC when you do not use multiple UOMs or multiple locations. closes odoo/odoo#95321 Signed-off-by: Raphael Collet <rco@odoo.com>
This commit is contained in:
@@ -38,6 +38,7 @@ class Repair(models.Model):
|
||||
readonly=True, required=True, states={'draft': [('readonly', False)]})
|
||||
product_uom = fields.Many2one(
|
||||
'uom.uom', 'Product Unit of Measure',
|
||||
compute='_compute_product_uom', store=True, precompute=True,
|
||||
readonly=True, required=True, states={'draft': [('readonly', False)]}, domain="[('category_id', '=', product_uom_category_id)]")
|
||||
product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id')
|
||||
partner_id = fields.Many2one(
|
||||
@@ -68,6 +69,7 @@ class Repair(models.Model):
|
||||
schedule_date = fields.Date("Scheduled Date")
|
||||
location_id = fields.Many2one(
|
||||
'stock.location', 'Location',
|
||||
compute="_compute_location_id", store=True, precompute=True,
|
||||
index=True, readonly=True, required=True, check_company=True,
|
||||
help="This is the location where the product to repair is located.",
|
||||
states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]})
|
||||
@@ -196,13 +198,17 @@ class Repair(models.Model):
|
||||
'warning': {'title': "Warning", 'message': "Note that the warehouse of the return and repair locations don't match!"},
|
||||
}
|
||||
|
||||
@api.depends('product_id')
|
||||
def _compute_product_uom(self):
|
||||
for repair in self:
|
||||
if repair.product_id:
|
||||
repair.product_uom = repair.product_id.uom_id
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_id(self):
|
||||
self.guarantee_limit = False
|
||||
if (self.product_id and self.lot_id and self.lot_id.product_id != self.product_id) or not self.product_id:
|
||||
self.lot_id = False
|
||||
if self.product_id:
|
||||
self.product_uom = self.product_id.uom_id.id
|
||||
|
||||
@api.onchange('product_uom')
|
||||
def onchange_product_uom(self):
|
||||
@@ -229,14 +235,15 @@ class Repair(models.Model):
|
||||
self.partner_invoice_id = addresses['invoice']
|
||||
self.pricelist_id = self.partner_id.property_product_pricelist.id
|
||||
|
||||
@api.onchange('company_id')
|
||||
def _onchange_company_id(self):
|
||||
if self.company_id:
|
||||
if self.location_id.company_id != self.company_id:
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.company_id.id)], limit=1)
|
||||
self.location_id = warehouse.lot_stock_id
|
||||
else:
|
||||
self.location_id = False
|
||||
@api.depends('company_id')
|
||||
def _compute_location_id(self):
|
||||
for order in self:
|
||||
if order.company_id:
|
||||
if order.location_id.company_id != order.company_id:
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', order.company_id.id)], limit=1)
|
||||
order.location_id = warehouse.lot_stock_id
|
||||
else:
|
||||
order.location_id = False
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_confirmed(self):
|
||||
@@ -676,6 +683,7 @@ class RepairLine(models.Model):
|
||||
digits='Product Unit of Measure', required=True)
|
||||
product_uom = fields.Many2one(
|
||||
'uom.uom', 'Product Unit of Measure',
|
||||
compute='_compute_product_uom', store=True, readonly=False, precompute=True,
|
||||
required=True, domain="[('category_id', '=', product_uom_category_id)]")
|
||||
product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id')
|
||||
invoice_line_id = fields.Many2one(
|
||||
@@ -683,9 +691,11 @@ class RepairLine(models.Model):
|
||||
copy=False, readonly=True, check_company=True)
|
||||
location_id = fields.Many2one(
|
||||
'stock.location', 'Source Location',
|
||||
compute='_compute_location_id', store=True, readonly=False, precompute=True,
|
||||
index=True, required=True, check_company=True)
|
||||
location_dest_id = fields.Many2one(
|
||||
'stock.location', 'Dest. Location',
|
||||
compute='_compute_location_id', store=True, readonly=False, precompute=True,
|
||||
index=True, required=True, check_company=True)
|
||||
move_id = fields.Many2one(
|
||||
'stock.move', 'Inventory Move',
|
||||
@@ -709,6 +719,26 @@ class RepairLine(models.Model):
|
||||
line.price_subtotal = taxes['total_excluded']
|
||||
line.price_total = taxes['total_included']
|
||||
|
||||
@api.depends('product_id')
|
||||
def _compute_product_uom(self):
|
||||
for line in self:
|
||||
line.product_uom = line.product_id.uom_id.id
|
||||
|
||||
@api.depends('type')
|
||||
def _compute_location_id(self):
|
||||
for line in self:
|
||||
if not line.type:
|
||||
line.location_id = False
|
||||
line.location_dest_id = False
|
||||
elif line.type == 'add':
|
||||
args = line.repair_id.company_id and [('company_id', '=', line.repair_id.company_id.id)] or []
|
||||
warehouse = line.env['stock.warehouse'].search(args, limit=1)
|
||||
line.location_id = warehouse.lot_stock_id
|
||||
line.location_dest_id = line.env['stock.location'].search([('usage', '=', 'production'), ('company_id', '=', line.repair_id.company_id.id)], limit=1)
|
||||
else:
|
||||
line.location_id = line.env['stock.location'].search([('usage', '=', 'production'), ('company_id', '=', line.repair_id.company_id.id)], limit=1).id
|
||||
line.location_dest_id = line.env['stock.location'].search([('scrap_location', '=', True), ('company_id', 'in', [line.repair_id.company_id.id, False])], limit=1).id
|
||||
|
||||
@api.onchange('type')
|
||||
def onchange_operation_type(self):
|
||||
""" On change of operation type it sets source location, destination location
|
||||
@@ -718,19 +748,12 @@ class RepairLine(models.Model):
|
||||
@return: Dictionary of values.
|
||||
"""
|
||||
if not self.type:
|
||||
self.location_id = False
|
||||
self.location_dest_id = False
|
||||
pass
|
||||
elif self.type == 'add':
|
||||
self.onchange_product_id()
|
||||
args = self.repair_id.company_id and [('company_id', '=', self.repair_id.company_id.id)] or []
|
||||
warehouse = self.env['stock.warehouse'].search(args, limit=1)
|
||||
self.location_id = warehouse.lot_stock_id
|
||||
self.location_dest_id = self.env['stock.location'].search([('usage', '=', 'production'), ('company_id', '=', self.repair_id.company_id.id)], limit=1)
|
||||
else:
|
||||
self.price_unit = 0.0
|
||||
self.tax_id = False
|
||||
self.location_id = self.env['stock.location'].search([('usage', '=', 'production'), ('company_id', '=', self.repair_id.company_id.id)], limit=1).id
|
||||
self.location_dest_id = self.env['stock.location'].search([('scrap_location', '=', True), ('company_id', 'in', [self.repair_id.company_id.id, False])], limit=1).id
|
||||
|
||||
@api.onchange('repair_id', 'product_id', 'product_uom_qty')
|
||||
def onchange_product_id(self):
|
||||
@@ -750,7 +773,6 @@ class RepairLine(models.Model):
|
||||
self.name += '\n' + self.product_id.with_context(lang=partner.lang).description_sale
|
||||
else:
|
||||
self.name += '\n' + self.product_id.description_sale
|
||||
self.product_uom = product.uom_id.id
|
||||
if self.type != 'remove':
|
||||
if partner:
|
||||
fpos = self.env['account.fiscal.position']._get_fiscal_position(partner_invoice, delivery=self.repair_id.address_id)
|
||||
|
||||
@@ -343,3 +343,36 @@ class TestRepair(AccountTestInvoicingCommon):
|
||||
for repair in return_picking.repair_ids:
|
||||
self.assertEqual(repair.location_id, return_picking.location_dest_id, "Repair location should have defaulted to return destination location")
|
||||
self.assertEqual(repair.partner_id, return_picking.partner_id, "Repair customer should have defaulted to return customer")
|
||||
|
||||
def test_repair_compute_product_uom(self):
|
||||
repair = self.env['repair.order'].create({
|
||||
'product_id': self.product_product_3.id,
|
||||
'operations': [
|
||||
(0, 0, {
|
||||
'name': 'foo',
|
||||
'product_id': self.product_product_11.id,
|
||||
'price_unit': 50.0,
|
||||
})
|
||||
],
|
||||
})
|
||||
self.assertEqual(repair.product_uom, self.product_product_3.uom_id)
|
||||
self.assertEqual(repair.operations[0].product_uom, self.product_product_11.uom_id)
|
||||
|
||||
def test_repair_compute_location(self):
|
||||
repair = self.env['repair.order'].create({
|
||||
'product_id': self.product_product_3.id,
|
||||
'operations': [
|
||||
(0, 0, {
|
||||
'name': 'foo',
|
||||
'product_id': self.product_product_11.id,
|
||||
'price_unit': 50.0,
|
||||
})
|
||||
],
|
||||
})
|
||||
self.assertEqual(repair.location_id, self.stock_warehouse.lot_stock_id)
|
||||
self.assertEqual(repair.operations[0].location_id, self.stock_warehouse.lot_stock_id)
|
||||
location_dest_id = self.env['stock.location'].search([
|
||||
('usage', '=', 'production'),
|
||||
('company_id', '=', repair.company_id.id),
|
||||
], limit=1)
|
||||
self.assertEqual(repair.operations[0].location_dest_id, location_dest_id)
|
||||
|
||||
@@ -1577,6 +1577,29 @@ class PrecomputeEditable(models.Model):
|
||||
record.baz2 = record.baz
|
||||
|
||||
|
||||
class PrecomputeReadonly(models.Model):
|
||||
_name = 'test_new_api.precompute.readonly'
|
||||
_description = 'a model with precomputed readonly fields'
|
||||
|
||||
foo = fields.Char()
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('confirmed', 'Confirmed'),
|
||||
], default='draft')
|
||||
bar = fields.Char(compute='_compute_bar', precompute=True, store=True, readonly=True)
|
||||
baz = fields.Char(
|
||||
compute='_compute_baz', precompute=True, store=True, readonly=True, states={'draft': [('readonly', False)]}
|
||||
)
|
||||
|
||||
@api.depends('foo')
|
||||
def _compute_bar(self):
|
||||
self.bar = "COMPUTED"
|
||||
|
||||
@api.depends('bar')
|
||||
def _compute_baz(self):
|
||||
self.baz = "COMPUTED"
|
||||
|
||||
|
||||
class PrecomputeRequired(models.Model):
|
||||
_name = 'test_new_api.precompute.required'
|
||||
_description = 'a model with precomputed required fields'
|
||||
|
||||
@@ -89,6 +89,7 @@ access_test_new_api_precompute,access_test_new_api_precompute,model_test_new_api
|
||||
access_test_new_api_precompute_line,access_test_new_api_precompute_line,model_test_new_api_precompute_line,,1,0,0,0
|
||||
access_test_new_api_precompute_combo,access_test_new_api_precompute_combo,model_test_new_api_precompute_combo,,1,0,0,0
|
||||
access_test_new_api_precompute_editable,access_test_new_api_precompute_editable,model_test_new_api_precompute_editable,,1,0,0,0
|
||||
access_test_new_api_precompute_readonly,access_test_new_api_precompute_readonly,model_test_new_api_precompute_readonly,,1,0,0,0
|
||||
access_test_new_api_precompute_required,access_test_new_api_precompute_required,model_test_new_api_precompute_required,,1,0,0,0
|
||||
access_test_new_api_precompute_monetary,access_test_new_api_precompute_monetary,model_test_new_api_precompute_monetary,,1,0,0,0
|
||||
access_test_new_api_prefetch,access_test_new_api_prefetch,model_test_new_api_prefetch,,1,0,0,0
|
||||
|
||||
|
@@ -3928,6 +3928,43 @@ class TestPrecompute(common.TransactionCase):
|
||||
self.assertEqual(record.baz, 'baz')
|
||||
self.assertEqual(record.baz2, 'baz')
|
||||
|
||||
def test_precompute_readonly(self):
|
||||
"""
|
||||
Ensures
|
||||
- a stored, precomputed, readonly field cannot be altered by the user,
|
||||
- a stored, precomputed, readonly field,
|
||||
but with a states attributes changing the readonly of the field according to the state of the record,
|
||||
can be altered by the user.
|
||||
The `bar` field is store=True, precompute=True, readonly=True
|
||||
The `baz` field is store=True, precompute=True, readonly=True, states={'draft': [('readonly', False)]}
|
||||
"""
|
||||
model = self.env['test_new_api.precompute.readonly']
|
||||
|
||||
# no value for bar, no value for baz
|
||||
record = model.create({'foo': 'foo'})
|
||||
self.assertEqual(record.bar, 'COMPUTED')
|
||||
self.assertEqual(record.baz, 'COMPUTED')
|
||||
|
||||
# value for bar, no value for baz
|
||||
# bar is readonly, it must ignore the value for bar in the create values
|
||||
record = model.create({'foo': 'foo', 'bar': 'bar'})
|
||||
self.assertEqual(record.bar, 'COMPUTED')
|
||||
self.assertEqual(record.baz, 'COMPUTED')
|
||||
|
||||
# no value for bar, value for baz
|
||||
# baz is readonly=True but states={'draft': [('readonly', False)]}
|
||||
# the value for baz must be taken into account
|
||||
record = model.create({'foo': 'foo', 'baz': 'baz'})
|
||||
self.assertEqual(record.bar, 'COMPUTED')
|
||||
self.assertEqual(record.baz, 'baz')
|
||||
|
||||
# value for bar, value for baz
|
||||
# bar must be ignored
|
||||
# baz must be taken into account
|
||||
record = model.create({'foo': 'foo', 'bar': 'bar', 'baz': 'baz'})
|
||||
self.assertEqual(record.bar, 'COMPUTED')
|
||||
self.assertEqual(record.baz, 'baz')
|
||||
|
||||
def test_precompute_required(self):
|
||||
model = self.env['test_new_api.precompute.required']
|
||||
|
||||
|
||||
@@ -4233,6 +4233,19 @@ class BaseModel(metaclass=MetaModel):
|
||||
fname
|
||||
for fname, field in self._fields.items()
|
||||
if field.precompute and field.readonly
|
||||
# ignore `readonly=True` when it's combined with the `states` attribute,
|
||||
# making the field readonly according to the record state.
|
||||
# e.g.
|
||||
# product_uom = fields.Many2one(
|
||||
# 'uom.uom', 'Product Unit of Measure',
|
||||
# compute='_compute_product_uom', store=True, precompute=True,
|
||||
# readonly=True, required=True, states={'draft': [('readonly', False)]},
|
||||
# )
|
||||
and (not field.states or not any(
|
||||
modifier == 'readonly'
|
||||
for modifiers in field.states.values()
|
||||
for modifier, _value in modifiers
|
||||
))
|
||||
)
|
||||
|
||||
result_vals_list = []
|
||||
|
||||
Reference in New Issue
Block a user