Skip to content
Merged
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_order_line_cancel/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"wizards/sale_order_line_cancel.xml",
"views/sale_order.xml",
"views/sale_order_line.xml",
"views/res_config_settings_views.xml",
],
"website": "https://github.com/OCA/sale-workflow",
"pre_init_hook": "pre_init_hook",
Expand Down
2 changes: 2 additions & 0 deletions sale_order_line_cancel/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from . import sale_order_line
from . import stock_move
from . import sale_order
from . import res_company
from . import res_config_settings
13 changes: 13 additions & 0 deletions sale_order_line_cancel/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2025 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models


class ResCompany(models.Model):
_inherit = "res.company"

on_sale_line_cancel_decrease_line_qty = fields.Boolean(
"On sale order line cancel decrease line quantity",
help="On canceling the remaining qty to deliver of an order line "
"it decreases the initial quantity",
)
11 changes: 11 additions & 0 deletions sale_order_line_cancel/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright 2025 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

on_sale_line_cancel_decrease_line_qty = fields.Boolean(
related="company_id.on_sale_line_cancel_decrease_line_qty", readonly=False
)
12 changes: 10 additions & 2 deletions sale_order_line_cancel/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2018 Okia SPRL
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# Copyright 2020 ACSONE SA/NV
# Copyright 2025 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
Expand Down Expand Up @@ -41,7 +42,7 @@ def _compute_can_cancel_remaining_qty(self):
@api.depends("qty_to_deliver", "product_qty_canceled")
def _compute_product_qty_remains_to_deliver(self):
for line in self:
qty_remaining = line.qty_to_deliver - line.product_qty_canceled
qty_remaining = max(0, line.qty_to_deliver - line.product_qty_canceled)
line.product_qty_remains_to_deliver = qty_remaining

def _get_moves_to_cancel(self):
Expand All @@ -57,7 +58,14 @@ def _update_qty_canceled(self):
for line in self:
if line._get_moves_to_cancel():
continue
line.product_qty_canceled = line.qty_to_deliver
qty_to_deliver = line.qty_to_deliver
vals = {"product_qty_canceled": qty_to_deliver}
if (
line.state == "sale"
and line.company_id.on_sale_line_cancel_decrease_line_qty
):
vals["product_uom_qty"] = line.qty_delivered
line.write(vals)

def cancel_remaining_qty(self):
lines = self.filtered(lambda l: l.can_cancel_remaining_qty)
Expand Down
26 changes: 26 additions & 0 deletions sale_order_line_cancel/tests/test_sale_order_line_cancel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2023 ACSONE SA/NV
# Copyright 2025 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).


Expand Down Expand Up @@ -140,3 +141,28 @@ def test_sent_sale_order_with_picking_cancel(self):
self.assertEqual(sale.order_line.product_qty_canceled, 0)
self.assertEqual(sale.order_line.qty_to_deliver, 10)
self.assertEqual(sale.order_line.product_qty_remains_to_deliver, 10)

def test_cancel_decrease_product_uom_qty(self):
sale = self.sale
sale.company_id.on_sale_line_cancel_decrease_line_qty = True
sale.with_context(disable_cancel_warning=True).action_cancel()
sale.picking_ids.unlink()
sale.action_draft()
sale.action_confirm()
line = self.sale.order_line
self.assertEqual(line.product_uom_qty, 10)
ship = self.sale.picking_ids
ship.action_assign()
ship.move_ids.move_line_ids.qty_done = 4
ship.with_context(cancel_backorder=False)._action_done()
moves_before = self.env["stock.move"].search([])
self.wiz.with_context(
active_id=line.id, active_model="sale.order.line"
).cancel_remaining_qty()
moves_after = moves_before.search([("id", "not in", moves_before.ids)])
self.assertFalse(moves_after)
self.assertEqual(line.product_qty_canceled, 6)
self.assertEqual(line.product_qty_remains_to_deliver, 0)
self.assertEqual(line.qty_to_deliver, 0)
self.assertEqual(line.qty_delivered, 4)
self.assertEqual(line.product_uom_qty, 4)
Comment thread
mt-software-de marked this conversation as resolved.
26 changes: 26 additions & 0 deletions sale_order_line_cancel/views/res_config_settings_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version='1.0' encoding='utf-8' ?>
<odoo>
<record id="res_config_settings_form_view" model="ir.ui.view">
<field name="name">res.config.settings.form.view</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@id='proforma_configuration']" position="after">
<div
class="col-12 col-lg-6 o_setting_box"
id="line_cancel_decrease_qty_configuration"
>
<div class="o_setting_left_pane">
<field name="on_sale_line_cancel_decrease_line_qty" />
</div>
<div class="o_setting_right_pane">
<label for="on_sale_line_cancel_decrease_line_qty" />
<div class="text-muted">
On canceling the remaining qty to deliver of an order line it decreases the initial quantity
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>