Skip to content

Commit 1f76dd8

Browse files
[FIX] sale_stock_product_pack: Add pack lines to invoices when components are returned
1 parent 657af34 commit 1f76dd8

File tree

3 files changed

+95
-24
lines changed

3 files changed

+95
-24
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from . import sale_order
2+
from . import sale_order_line

sale_stock_product_pack/models/sale_order.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,39 @@
44
from odoo import models
55

66

7-
class SaleOrderLine(models.Model):
8-
_inherit = "sale.order.line"
7+
class SaleOrder(models.Model):
8+
_inherit = "sale.order"
99

10-
def _compute_qty_delivered(self):
11-
"""Compute pack delivered pack quantites according to its components
12-
deliveries"""
13-
super()._compute_qty_delivered()
14-
main_pack_lines = self.filtered("pack_parent_line_id").mapped(
15-
"pack_parent_line_id"
16-
)
17-
for line in main_pack_lines.filtered(
18-
lambda x: x.qty_delivered_method == "stock_move"
19-
and x.pack_child_line_ids
20-
and x.product_uom_qty
21-
):
22-
delivered_packs = []
23-
# We filter non qty lines of editable packs
24-
for pack_line in line.pack_child_line_ids.filtered("product_uom_qty"):
25-
# If a component isn't delivered, the pack isn't as well
26-
if not pack_line.qty_delivered:
27-
delivered_packs.append(0)
28-
break
29-
qty_per_pack = pack_line.product_uom_qty / line.product_uom_qty
30-
delivered_packs.append(pack_line.qty_delivered / qty_per_pack)
31-
line.qty_delivered = delivered_packs and min(delivered_packs) or 0.0
10+
def _get_invoiceable_lines(self, final=False):
11+
"""Override to ensure pack parent lines are included when their
12+
components are being invoiced/refunded.
13+
14+
When processing returns, only component lines have stock moves, so only
15+
they would be included in the refund. This method ensures that if pack
16+
component lines are invoiceable (due to returns), their parent pack line
17+
is also included if it has a price.
18+
"""
19+
lines = super()._get_invoiceable_lines(final=final)
20+
pack_components_in_lines = lines.filtered("pack_parent_line_id")
21+
if pack_components_in_lines:
22+
pack_parent_lines = pack_components_in_lines.mapped("pack_parent_line_id")
23+
has_return = any(
24+
comp.qty_to_invoice < 0 for comp in pack_components_in_lines
25+
)
26+
additional_pack_lines = pack_parent_lines.filtered(
27+
lambda x: x.id not in lines.ids
28+
and (x.pack_component_price != "detailed" or x.price_unit > 0)
29+
and (x.qty_to_invoice != 0 or (x.qty_invoiced > 0 and has_return))
30+
)
31+
if additional_pack_lines:
32+
result_lines = self.env["sale.order.line"]
33+
for line in lines:
34+
result_lines |= line
35+
if (
36+
line.pack_parent_line_id
37+
and line.pack_parent_line_id in additional_pack_lines
38+
):
39+
result_lines |= line.pack_parent_line_id
40+
additional_pack_lines -= line.pack_parent_line_id
41+
return result_lines
42+
return lines
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright 2021 Tecnativa - David Vidal
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
# pylint: disable=W8110
4+
from odoo import models
5+
6+
7+
class SaleOrderLine(models.Model):
8+
_inherit = "sale.order.line"
9+
10+
def _compute_qty_delivered(self):
11+
"""Compute pack delivered pack quantites according to its components
12+
deliveries"""
13+
super()._compute_qty_delivered()
14+
main_pack_lines = self.filtered("pack_parent_line_id").mapped(
15+
"pack_parent_line_id"
16+
)
17+
for line in main_pack_lines.filtered(
18+
lambda x: x.qty_delivered_method == "stock_move"
19+
and x.pack_child_line_ids
20+
and x.product_uom_qty
21+
):
22+
delivered_packs = []
23+
# We filter non qty lines of editable packs
24+
for pack_line in line.pack_child_line_ids.filtered("product_uom_qty"):
25+
# If a component isn't delivered, the pack isn't as well
26+
if not pack_line.qty_delivered:
27+
delivered_packs.append(0)
28+
break
29+
qty_per_pack = pack_line.product_uom_qty / line.product_uom_qty
30+
delivered_packs.append(pack_line.qty_delivered / qty_per_pack)
31+
line.qty_delivered = delivered_packs and min(delivered_packs) or 0.0
32+
33+
def _prepare_invoice_line(self, **optional_values):
34+
"""Override to handle pack lines in returns.
35+
36+
When a return is being processed, pack parent lines don't have stock moves,
37+
so their qty_delivered doesn't update automatically. We need to calculate
38+
the correct quantity based on the components being returned.
39+
"""
40+
res = super()._prepare_invoice_line(**optional_values)
41+
if self.pack_child_line_ids and self.product_id.pack_ok:
42+
components_being_returned = self.pack_child_line_ids.filtered(
43+
lambda x: x.qty_to_invoice < 0
44+
)
45+
if components_being_returned and res.get("quantity", 0) == 0:
46+
refund_quantities = []
47+
for comp in components_being_returned:
48+
qty_per_pack = (
49+
comp.product_uom_qty / self.product_uom_qty
50+
if self.product_uom_qty
51+
else 0
52+
)
53+
if qty_per_pack:
54+
packs_to_refund = comp.qty_to_invoice / qty_per_pack
55+
refund_quantities.append(packs_to_refund)
56+
if refund_quantities:
57+
pack_qty_to_refund = max(refund_quantities)
58+
res["quantity"] = pack_qty_to_refund
59+
return res

0 commit comments

Comments
 (0)