[FIX] purchase/sale: filter on company in package computation

Versions:
---------
- 16.0+

Steps to reproduce:
-------------------
1. install stock & purchase;
2. in Inventory settings, enable product packaging;
3. create a packaging for a product;
4. assign the packaging to the current company;
5. switch to a different company;
6. create a purchase order with the product.

Issue:
------
The packaging that gets selected automatically shouldn't be accessible
to the current company (and isn't in the drop-down). Trying to save
the form will return an access error.
Similar issues arise in `sale`.

Cause:
------
726179af78 and
ccdd113260 updated product packaging
onchange methods to computed methods, changing the environment these
fields exist to a `sudo` one. As a consequence, it includes records that
are normally inaccessible.

Solution:
---------
Use the follwing filter before calling `_find_suitable_packaging`:
```python
lambda p: p.product_id.company_id <= p.company_id <= line.company_id
```
- if `p.product_id.company_id` is set, `p.company_id` has to be set too;
- else `p.company_id` has to be `False` or equal to `line.company_id`.

opw-3457782

closes odoo/odoo#140025

X-original-commit: 05f6e91cd9babd8d65e2526a17439a7d81c65fc9
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: Levi Siuzdak <sile@odoo.com>
This commit is contained in:
Levi Siuzdak (sile)
2023-10-27 14:22:03 +00:00
parent 10ede45cf5
commit d9c043e9bf
4 changed files with 88 additions and 8 deletions
@@ -377,9 +377,12 @@ class PurchaseOrderLine(models.Model):
# remove packaging if not match the product
if line.product_packaging_id.product_id != line.product_id:
line.product_packaging_id = False
# suggest biggest suitable packaging
# suggest biggest suitable packaging matching the PO's company
if line.product_id and line.product_qty and line.product_uom:
line.product_packaging_id = line.product_id.packaging_ids.filtered('purchase')._find_suitable_product_packaging(line.product_qty, line.product_uom) or line.product_packaging_id
suggested_packaging = line.product_id.packaging_ids\
.filtered(lambda p: p.purchase and (p.product_id.company_id <= p.company_id <= line.company_id))\
._find_suitable_product_packaging(line.product_qty, line.product_uom)
line.product_packaging_id = suggested_packaging or line.product_packaging_id
@api.onchange('product_packaging_id')
def _onchange_product_packaging_id(self):
+39 -2
View File
@@ -174,7 +174,7 @@ class TestPurchase(AccountTestInvoicingCommon):
activity.note,
)
def test_onchange_packaging_00(self):
def test_compute_packaging_00(self):
"""Create a PO and use packaging. Check we suggested suitable packaging
according to the product_qty. Also check product_qty or product_packaging
are correctly calculated when one of them changed.
@@ -219,7 +219,7 @@ class TestPurchase(AccountTestInvoicingCommon):
self.assertEqual(po.order_line.product_qty, 12)
# Do the same test but without form, to check the `product_packaging_id` and `product_packaging_qty` are set
# without manual call to onchanges
# without manual call to compute
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
@@ -237,6 +237,43 @@ class TestPurchase(AccountTestInvoicingCommon):
po.order_line.product_packaging_qty = 1.0
self.assertEqual(po.order_line.product_qty, 12)
def test_compute_packaging_01(self):
"""Create a PO and use packaging in a multicompany environment.
Ensure any suggested packaging matches the PO's.
"""
company1 = self.company_data['company']
company2 = self.company_data_2['company']
generic_single_pack = self.env['product.packaging'].create({
'name': "single pack",
'product_id': self.product_a.id,
'qty': 1.0,
'company_id': False,
})
company2_pack_of_10 = self.env['product.packaging'].create({
'name': "pack of 10 by Company 2",
'product_id': self.product_a.id,
'qty': 10.0,
'company_id': company2.id,
})
po1 = self.env['purchase.order'].with_company(company1).create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({'product_id': self.product_a.id, 'product_qty': 10.0}),
]
})
self.assertEqual(po1.order_line.product_packaging_id, generic_single_pack)
self.assertEqual(po1.order_line.product_packaging_qty, 10.0)
# verify that with the right company, we can get the other packaging
po2 = self.env['purchase.order'].with_company(company2).create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({'product_id': self.product_a.id, 'product_qty': 10.0}),
]
})
self.assertEqual(po2.order_line.product_packaging_id, company2_pack_of_10)
self.assertEqual(po2.order_line.product_packaging_qty, 1.0)
def test_with_different_uom(self):
""" This test ensures that the unit price is correctly computed"""
+5 -3
View File
@@ -642,10 +642,12 @@ class SaleOrderLine(models.Model):
# remove packaging if not match the product
if line.product_packaging_id.product_id != line.product_id:
line.product_packaging_id = False
# Find biggest suitable packaging
# suggest biggest suitable packaging matching the SO's company
if line.product_id and line.product_uom_qty and line.product_uom:
line.product_packaging_id = line.product_id.packaging_ids.filtered(
'sales')._find_suitable_product_packaging(line.product_uom_qty, line.product_uom) or line.product_packaging_id
suggested_packaging = line.product_id.packaging_ids\
.filtered(lambda p: p.sales and (p.product_id.company_id <= p.company_id <= line.company_id))\
._find_suitable_product_packaging(line.product_uom_qty, line.product_uom)
line.product_packaging_id = suggested_packaging or line.product_packaging_id
@api.depends('product_packaging_id', 'product_uom', 'product_uom_qty')
def _compute_product_packaging_qty(self):
+39 -1
View File
@@ -172,7 +172,7 @@ class TestSaleOrder(SaleCommon):
with self.assertRaises(UserError):
self.sale_order.unlink()
def test_onchange_packaging_00(self):
def test_compute_packaging_00(self):
"""Create a SO and use packaging. Check we suggested suitable packaging
according to the product_qty. Also check product_qty or product_packaging
are correctly calculated when one of them changed.
@@ -253,6 +253,44 @@ class TestSaleOrder(SaleCommon):
self.assertEqual(so2.order_line.product_packaging_qty, 1)
self.assertEqual(so2.order_line.product_packaging_id.id, packaging_pack_of_20.id)
def test_compute_packaging_01(self):
"""Create a SO and use packaging in a multicompany environment.
Ensure any suggested packaging matches the SO's.
"""
company2 = self.env['res.company'].create([{'name': 'Company 2'}])
generic_single_pack = self.env['product.packaging'].create({
'name': "single pack",
'product_id': self.product.id,
'qty': 1.0,
'company_id': False,
})
company2_pack_of_10 = self.env['product.packaging'].create({
'name': "pack of 10 by Company 2",
'product_id': self.product.id,
'qty': 10.0,
'company_id': company2.id,
})
so1 = self.empty_order
so1_form = Form(so1)
with so1_form.order_line.new() as line:
line.product_id = self.product
line.product_uom_qty = 10.0
so1_form.save()
self.assertEqual(so1.order_line.product_packaging_id, generic_single_pack)
self.assertEqual(so1.order_line.product_packaging_qty, 10.0)
so2 = self.env['sale.order'].with_company(company2).create({
'partner_id': self.partner.id,
})
so2_form = Form(so2)
with so2_form.order_line.new() as line:
line.product_id = self.product
line.product_uom_qty = 10.0
so2_form.save()
self.assertEqual(so2.order_line.product_packaging_id, company2_pack_of_10)
self.assertEqual(so2.order_line.product_packaging_qty, 1.0)
def _create_sale_order(self):
"""Create dummy sale order (without lines)"""
return self.env['sale.order'].with_context(