[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:
Denis Ledoux
2022-07-07 17:30:35 +02:00
parent c779ee3f26
commit f2f5ce7790
6 changed files with 148 additions and 19 deletions
+41 -19
View File
@@ -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)
+33
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
89 access_test_new_api_precompute_line access_test_new_api_precompute_line model_test_new_api_precompute_line 1 0 0 0
90 access_test_new_api_precompute_combo access_test_new_api_precompute_combo model_test_new_api_precompute_combo 1 0 0 0
91 access_test_new_api_precompute_editable access_test_new_api_precompute_editable model_test_new_api_precompute_editable 1 0 0 0
92 access_test_new_api_precompute_readonly access_test_new_api_precompute_readonly model_test_new_api_precompute_readonly 1 0 0 0
93 access_test_new_api_precompute_required access_test_new_api_precompute_required model_test_new_api_precompute_required 1 0 0 0
94 access_test_new_api_precompute_monetary access_test_new_api_precompute_monetary model_test_new_api_precompute_monetary 1 0 0 0
95 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']
+13
View File
@@ -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 = []