diff --git a/sale_stock_product_pack/models/__init__.py b/sale_stock_product_pack/models/__init__.py index 6aacb7531..2d7ee6c3d 100644 --- a/sale_stock_product_pack/models/__init__.py +++ b/sale_stock_product_pack/models/__init__.py @@ -1 +1,2 @@ from . import sale_order +from . import sale_order_line diff --git a/sale_stock_product_pack/models/sale_order.py b/sale_stock_product_pack/models/sale_order.py index 02df5ff1b..c9fd7e96a 100644 --- a/sale_stock_product_pack/models/sale_order.py +++ b/sale_stock_product_pack/models/sale_order.py @@ -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) + 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 diff --git a/sale_stock_product_pack/models/sale_order_line.py b/sale_stock_product_pack/models/sale_order_line.py new file mode 100644 index 000000000..166fe6eb0 --- /dev/null +++ b/sale_stock_product_pack/models/sale_order_line.py @@ -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 + 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