diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index ba886f1e7cbc4869f809c1204b2937c08486d86a..9c47841fdcc3f868e350811245fa2fe808dcf390 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -29,7 +29,7 @@ class PurchaseOrder(models.Model): is_shipped = fields.Boolean(compute="_compute_is_shipped") effective_date = fields.Datetime("Effective Date", compute='_compute_effective_date', store=True, copy=False, help="Completion date of the first receipt order.") - on_time_rate = fields.Float(related='partner_id.on_time_rate') + on_time_rate = fields.Float(related='partner_id.on_time_rate', compute_sudo=False) @api.depends('order_line.move_ids.picking_id') def _compute_picking(self): diff --git a/addons/purchase_stock/tests/test_purchase_order.py b/addons/purchase_stock/tests/test_purchase_order.py index 90aae7a98d15331d17cd4944770b9c9e58cfd4bf..ddb5dd80ceb2d5c4c68d8b99b89dff30d9558c6a 100644 --- a/addons/purchase_stock/tests/test_purchase_order.py +++ b/addons/purchase_stock/tests/test_purchase_order.py @@ -263,3 +263,68 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon): self.assertEqual(po.company_id, company_a) self.assertEqual(po.picking_type_id.warehouse_id.company_id, company_a) self.assertEqual(po.currency_id, po.company_id.currency_id) + + def test_06_on_time_rate(self): + company_a = self.env.user.company_id + company_b = self.env['res.company'].create({ + "name": "Test Company", + "currency_id": self.env['res.currency'].with_context(active_test=False).search([ + ('id', '!=', company_a.currency_id.id), + ], limit=1).id + }) + + # Create a purchase order with 90% qty received for company A + self.env.user.write({ + 'company_id': company_a.id, + 'company_ids': [(6, 0, [company_a.id])], + }) + po = self.env['purchase.order'].create(self.po_vals) + po.order_line.write({'product_qty': 10}) + po.button_confirm() + picking = po.picking_ids[0] + # Process 9.0 out of the 10.0 ordered qty + picking.move_line_ids.write({'qty_done': 9.0}) + res_dict = picking.button_validate() + # No backorder + self.env['stock.backorder.confirmation'].with_context(res_dict['context']).process_cancel_backorder() + # `on_time_rate` should be equals to the ratio of quantity received against quantity ordered + expected_rate = sum(picking.move_line_ids.mapped("qty_done")) / sum(po.order_line.mapped("product_qty")) * 100 + self.assertEqual(expected_rate, po.on_time_rate) + + # Create a purchase order with 80% qty received for company B + # The On-Time Delivery Rate shouldn't be shared accross multiple companies + self.env.user.write({ + 'company_id': company_b.id, + 'company_ids': [(6, 0, [company_b.id])], + }) + po = self.env['purchase.order'].create(self.po_vals) + po.order_line.write({'product_qty': 10}) + po.button_confirm() + picking = po.picking_ids[0] + # Process 8.0 out of the 10.0 ordered qty + picking.move_line_ids.write({'qty_done': 8.0}) + res_dict = picking.button_validate() + # No backorder + self.env['stock.backorder.confirmation'].with_context(res_dict['context']).process_cancel_backorder() + # `on_time_rate` should be equal to the ratio of quantity received against quantity ordered + expected_rate = sum(picking.move_line_ids.mapped("qty_done")) / sum(po.order_line.mapped("product_qty")) * 100 + self.assertEqual(expected_rate, po.on_time_rate) + + # Tricky corner case + # As `purchase.order.on_time_rate` is a related to `partner_id.on_time_rate` + # `on_time_rate` on the PO should equals `on_time_rate` on the partner. + # Related fields are by default computed as sudo + # while non-stored computed fields are not computed as sudo by default + # If the computation of the related field (`purchase.order.on_time_rate`) was asked + # and `res.partner.on_time_rate` was not yet in the cache + # the `sudo` requested for the computation of the related `purchase.order.on_time_rate` + # was propagated to the computation of `res.partner.on_time_rate` + # and therefore the multi-company record rules were ignored. + # 1. Compute `res.partner.on_time_rate` regular non-stored comptued field + partner_on_time_rate = po.partner_id.on_time_rate + # 2. Invalidate the cache for that record and field, so it's not reused in the next step. + po.partner_id.invalidate_cache(fnames=["on_time_rate"], ids=po.partner_id.ids) + # 3. Compute the related field `purchase.order.on_time_rate` + po_on_time_rate = po.on_time_rate + # 4. Check both are equals. + self.assertEqual(partner_on_time_rate, po_on_time_rate)