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
1 change: 1 addition & 0 deletions sale_stock_product_pack/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import sale_order
from . import sale_order_line
59 changes: 35 additions & 24 deletions sale_stock_product_pack/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,39 @@
from odoo import models


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
class SaleOrder(models.Model):
_inherit = "sale.order"

def _compute_qty_delivered(self):
"""Compute pack delivered pack quantites according to its components
deliveries"""
super()._compute_qty_delivered()
main_pack_lines = self.filtered("pack_parent_line_id").mapped(
"pack_parent_line_id"
)
for line in main_pack_lines.filtered(
lambda x: x.qty_delivered_method == "stock_move"
and x.pack_child_line_ids
and x.product_uom_qty
):
delivered_packs = []
# We filter non qty lines of editable packs
for pack_line in line.pack_child_line_ids.filtered("product_uom_qty"):
# If a component isn't delivered, the pack isn't as well
if not pack_line.qty_delivered:
delivered_packs.append(0)
break
qty_per_pack = pack_line.product_uom_qty / line.product_uom_qty
delivered_packs.append(pack_line.qty_delivered / qty_per_pack)
line.qty_delivered = delivered_packs and min(delivered_packs) or 0.0
def _get_invoiceable_lines(self, final=False):
"""Override to ensure pack parent lines are included when their
components are being invoiced/refunded.

When processing returns, only component lines have stock moves, so only
they would be included in the refund. This method ensures that if pack
component lines are invoiceable (due to returns), their parent pack line
is also included if it has a price.
"""
lines = super()._get_invoiceable_lines(final=final)
Comment on lines +10 to +19
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

New return/refund invoicing behavior is introduced here (injecting pack parent lines when components are refunded), but there are no tests covering the credit note/invoice line output. Please add a test that performs: deliver a pack, invoice it, process a return on components, create the refund, and assert the resulting credit note includes the pack parent line with the expected quantity/amount.

Copilot uses AI. Check for mistakes.
pack_components_in_lines = lines.filtered("pack_parent_line_id")
if pack_components_in_lines:
pack_parent_lines = pack_components_in_lines.mapped("pack_parent_line_id")
has_return = any(
comp.qty_to_invoice < 0 for comp in pack_components_in_lines
)
additional_pack_lines = pack_parent_lines.filtered(
lambda x: x.id not in lines.ids
and (x.pack_component_price != "detailed" or x.price_unit > 0)
and (x.qty_to_invoice != 0 or (x.qty_invoiced > 0 and has_return))
)
if additional_pack_lines:
result_lines = self.env["sale.order.line"]
for line in lines:
result_lines |= line
if (
line.pack_parent_line_id
and line.pack_parent_line_id in additional_pack_lines
):
result_lines |= line.pack_parent_line_id
additional_pack_lines -= line.pack_parent_line_id
return result_lines
return lines
59 changes: 59 additions & 0 deletions sale_stock_product_pack/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2021 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=W8110
from odoo import models


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

def _compute_qty_delivered(self):
"""Compute pack delivered pack quantites according to its components
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

Docstring typo: "quantites" should be "quantities".

Suggested change
"""Compute pack delivered pack quantites according to its components
"""Compute pack delivered pack quantities according to its components

Copilot uses AI. Check for mistakes.
deliveries"""
super()._compute_qty_delivered()
main_pack_lines = self.filtered("pack_parent_line_id").mapped(
"pack_parent_line_id"
)
for line in main_pack_lines.filtered(
lambda x: x.qty_delivered_method == "stock_move"
and x.pack_child_line_ids
and x.product_uom_qty
):
delivered_packs = []
# We filter non qty lines of editable packs
for pack_line in line.pack_child_line_ids.filtered("product_uom_qty"):
# If a component isn't delivered, the pack isn't as well
if not pack_line.qty_delivered:
delivered_packs.append(0)
break
qty_per_pack = pack_line.product_uom_qty / line.product_uom_qty
delivered_packs.append(pack_line.qty_delivered / qty_per_pack)
line.qty_delivered = delivered_packs and min(delivered_packs) or 0.0

def _prepare_invoice_line(self, **optional_values):
"""Override to handle pack lines in returns.

When a return is being processed, pack parent lines don't have stock moves,
so their qty_delivered doesn't update automatically. We need to calculate
the correct quantity based on the components being returned.
"""
res = super()._prepare_invoice_line(**optional_values)
if self.pack_child_line_ids and self.product_id.pack_ok:
components_being_returned = self.pack_child_line_ids.filtered(
lambda x: x.qty_to_invoice < 0
)
if components_being_returned and res.get("quantity", 0) == 0:
refund_quantities = []
for comp in components_being_returned:
qty_per_pack = (
comp.product_uom_qty / self.product_uom_qty
if self.product_uom_qty
else 0
)
if qty_per_pack:
packs_to_refund = comp.qty_to_invoice / qty_per_pack
refund_quantities.append(packs_to_refund)
if refund_quantities:
pack_qty_to_refund = max(refund_quantities)
res["quantity"] = pack_qty_to_refund
return res