Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sale_product_pack/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Sale Product Pack",
"version": "17.0.2.0.1",
"version": "17.0.2.0.2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is not necessary if no migration scripts are added, when something is merged, bot does the version change automatically.

"category": "Sales",
"summary": "This module allows you to sell product packs",
"website": "https://github.com/OCA/product-pack",
Expand Down
1 change: 1 addition & 0 deletions sale_product_pack/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import models
from . import product_pack_line
from . import sale_order_line
from . import sale_order
29 changes: 29 additions & 0 deletions sale_product_pack/models/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2023 Foodles (http://www.foodles.co).
# @author Pierre Verkest <pierreverkest84@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from collections import defaultdict

from odoo import models


class BaseModel(models.AbstractModel):
_inherit = "base"

def group_recordset_by(self, key):
"""Return a collection of pairs ``(key, recordset)`` from ``self``. The
``key`` is a function computing a key value for each element. This
function is similar to ``itertools.groupby``, but aggregates all
elements under the same key, not only consecutive elements.
it's also similar to ``òdoo.tools.misc.groupby`` but return a recordset
of sale.order.line instead list
this let write some code likes this::
my_recordset.filtered(
lambda record: record.to_use
).group_recordset_by(
lambda record: record.group_key
)
"""
groups = defaultdict(self.env[self._name].browse)
for elem in self:
groups[key(elem)] |= elem
return groups.items()
20 changes: 0 additions & 20 deletions sale_product_pack/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,6 @@ def check_pack_line_unlink(self):
)
)

def write(self, vals):
if "order_line" in vals:
to_delete_ids = [e[1] for e in vals["order_line"] if e[0] == 2]
subpacks_to_delete_ids = (
self.env["sale.order.line"]
.search(
[("id", "child_of", to_delete_ids), ("id", "not in", to_delete_ids)]
)
.ids
)
if subpacks_to_delete_ids:
for cmd in vals["order_line"]:
if cmd[1] in subpacks_to_delete_ids:
if cmd[0] != 2:
cmd[0] = 2
subpacks_to_delete_ids.remove(cmd[1])
for to_delete_id in subpacks_to_delete_ids:
vals["order_line"].append([2, to_delete_id, False])
return super().write(vals)

def _get_update_prices_lines(self):
res = super()._get_update_prices_lines()
return res.filtered(
Expand Down
27 changes: 26 additions & 1 deletion sale_product_pack/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright 2019 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.fields import first
Expand Down Expand Up @@ -93,9 +94,21 @@
else:
return super().create(vals_list)

@api.model
def _pack_fields_trigger_expand_pack_line_on_write(self):
"""A set of fields that will trigger expand pack line
To propagate information over sale order line add your
field in this list and overload the following method:
`ProductPack.get_sale_order_line_vals`
Be aware if pack line are "modifiable" user input can
be overwrite once save if one of this field as been
changed on the pack line...
"""
return {"product_id", "product_uom_qty"}

def write(self, vals):
res = super().write(vals)
if "product_id" in vals or "product_uom_qty" in vals:
if self._pack_fields_trigger_expand_pack_line_on_write() & set(vals.keys()):
for record in self:
record.expand_pack_line(write=True)
return res
Expand Down Expand Up @@ -167,3 +180,15 @@
for pack_line in self.filtered("pack_parent_line_id"):
pack_line.discount = pack_line._get_pack_line_discount()
return res

def unlink(self):
for order, lines in self.group_recordset_by(lambda sol: sol.order_id):
pack_component_to_delete = self.search(

Check warning on line 186 in sale_product_pack/models/sale_order_line.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/models/sale_order_line.py#L186

Added line #L186 was not covered by tests
[
("id", "child_of", lines.ids),
("id", "not in", lines.ids),
("order_id", "=", order.id),
]
)
pack_component_to_delete.unlink()

Check warning on line 193 in sale_product_pack/models/sale_order_line.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/models/sale_order_line.py#L193

Added line #L193 was not covered by tests
return super().unlink()
200 changes: 200 additions & 0 deletions sale_product_pack/tests/test_sale_product_pack.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Copyright 2019 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo.exceptions import UserError
from odoo.tests import Form

from odoo.addons.base.tests.common import BaseCommon


Expand Down Expand Up @@ -221,6 +224,105 @@
total_qty_confirmed = qty_in_order()
self.assertAlmostEqual(total_qty_updated * 2, total_qty_confirmed)

def test_update_qty_do_not_expand(self):
product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components")
main_sol = self.env["sale.order.line"].create(
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)
main_sol.with_context(update_prices=True).product_uom_qty = 2
self.assertTrue(
all(
self.sale_order.order_line.filtered(
lambda sol: sol.pack_parent_line_id == main_sol
).mapped(lambda sol: sol.product_uom_qty == 1)
),
)

def test_update_pack_qty_with_new_component(self):
product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components")
main_sol = self.env["sale.order.line"].create(
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)

self.assertEqual(
sum(
self.sale_order.order_line.filtered(
lambda sol: sol.pack_parent_line_id == main_sol
).mapped("product_uom_qty")
),
3,
"Expected 3 lines with quantity 1 while setup this test",
)

product_cp.pack_line_ids |= self.env["product.pack.line"].create(
{
"parent_product_id": product_cp.id,
"product_id": self.env.ref("product.product_product_12").id,
"quantity": 2,
}
)

main_sol.product_uom_qty = 2
self.assertEqual(
sum(
self.sale_order.order_line.filtered(
lambda sol: sol.pack_parent_line_id == main_sol
).mapped("product_uom_qty")
),
10,
"Expected 3 lines with quantity 2 and new component line with quantity 4",
)

def test_update_pack_qty_with_new_component_do_not_expand(self):
product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components")
main_sol = self.env["sale.order.line"].create(
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)

self.assertEqual(
sum(
self.sale_order.order_line.filtered(
lambda sol: sol.pack_parent_line_id == main_sol
).mapped("product_uom_qty")
),
3,
"Expected 3 lines with quantity 1 while setup this test",
)

product_cp.pack_line_ids |= self.env["product.pack.line"].create(
{
"parent_product_id": product_cp.id,
"product_id": self.env.ref("product.product_product_12").id,
"quantity": 2,
}
)

main_sol.with_context(update_prices=True).product_uom_qty = 2
self.assertEqual(
sum(
self.sale_order.order_line.filtered(
lambda sol: sol.pack_parent_line_id == main_sol
).mapped("product_uom_qty")
),
3,
"Expected 3 lines with quantity 2 and no new component line",
)

def test_do_not_expand(self):
product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components")
pack_line = self.env["sale.order.line"].create(
Expand Down Expand Up @@ -370,3 +472,101 @@
[19, 19, 10],
"Discounts for the pack lines are not calculated correctly.",
)

def test_copy_sale_order_with_detailed_product_pack(self):
product_cp = self.env.ref(

Check warning on line 477 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L477

Added line #L477 was not covered by tests
"product_pack.product_pack_cpu_detailed_components"
)
self.env["sale.order.line"].create(

Check warning on line 480 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L480

Added line #L480 was not covered by tests
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)
copied_order = self.sale_order.copy()
copied_order_component_lines_pack_line = copied_order.order_line.filtered(

Check warning on line 489 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L488-L489

Added lines #L488 - L489 were not covered by tests
lambda line: line.product_id.pack_ok
)
copied_order_component_lines = copied_order.order_line.filtered(

Check warning on line 492 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L492

Added line #L492 was not covered by tests
lambda line: line.pack_parent_line_id
)
self.assertEqual(

Check warning on line 495 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L495

Added line #L495 was not covered by tests
copied_order_component_lines.pack_parent_line_id,
copied_order_component_lines_pack_line,
)

def test_check_pack_line_unlink(self):
product_cp = self.env.ref(

Check warning on line 501 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L501

Added line #L501 was not covered by tests
"product_pack.product_pack_cpu_detailed_components"
)
self.env["sale.order.line"].create(

Check warning on line 504 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L504

Added line #L504 was not covered by tests
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)
with Form(self.sale_order) as so_form:
with self.assertRaisesRegex(

Check warning on line 513 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L512-L513

Added lines #L512 - L513 were not covered by tests
UserError,
"You cannot delete this line because is part of a pack in this "
"sale order. In order to delete this line you need to delete the "
"pack itself",
):
so_form.order_line.remove(len(self.sale_order.order_line) - 1)

Check warning on line 519 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L519

Added line #L519 was not covered by tests

def test_unlink_pack_form_proxy(self):
product_cp = self.env.ref(

Check warning on line 522 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L522

Added line #L522 was not covered by tests
"product_pack.product_pack_cpu_detailed_components"
)
self.env["sale.order.line"].create(

Check warning on line 525 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L525

Added line #L525 was not covered by tests
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)
with Form(self.sale_order) as so_form:
so_form.order_line.remove(0)
so_form.save()
self.assertEqual(len(self.sale_order.order_line), 0)

Check warning on line 536 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L533-L536

Added lines #L533 - L536 were not covered by tests

def test_unlink_pack_record_unlink(self):
product_cp = self.env.ref(

Check warning on line 539 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L539

Added line #L539 was not covered by tests
"product_pack.product_pack_cpu_detailed_components"
)
self.env["sale.order.line"].create(

Check warning on line 542 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L542

Added line #L542 was not covered by tests
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)
pack_line = self.sale_order.order_line.filtered(

Check warning on line 550 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L550

Added line #L550 was not covered by tests
lambda line: line.product_id.pack_ok
)
pack_line.unlink()
self.assertEqual(len(self.sale_order.order_line), 0)

Check warning on line 554 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L553-L554

Added lines #L553 - L554 were not covered by tests

def test_unlink_pack_old_style_like_ui(self):
product_cp = self.env.ref(

Check warning on line 557 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L557

Added line #L557 was not covered by tests
"product_pack.product_pack_cpu_detailed_components"
)
self.env["sale.order.line"].create(

Check warning on line 560 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L560

Added line #L560 was not covered by tests
{
"order_id": self.sale_order.id,
"name": product_cp.name,
"product_id": product_cp.id,
"product_uom_qty": 1,
}
)
pack_line = self.sale_order.order_line.filtered(

Check warning on line 568 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L568

Added line #L568 was not covered by tests
lambda line: line.product_id.pack_ok
)
self.sale_order.write({"order_line": [(2, pack_line.id)]})
self.assertEqual(len(self.sale_order.order_line), 0)

Check warning on line 572 in sale_product_pack/tests/test_sale_product_pack.py

View check run for this annotation

Codecov / codecov/patch

sale_product_pack/tests/test_sale_product_pack.py#L571-L572

Added lines #L571 - L572 were not covered by tests
Loading
Loading