From 419a7b46f7bcf6fe90b358a7b578520bd4cd4db0 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Tue, 5 May 2026 16:09:51 +0200 Subject: [PATCH 1/3] [IMP] pos_sale_picking_keep: Allow to choose a strategy to keep or not pickings As in some cases, you want to mix in POS orders the payment of an existing sale and added products in the same POS order, you still want to keep both pickings linked to sale orders AND pos orders. This introduces the keep picking strategy that allows to choose per POS configuration the strategy that fits the best to your case. --- pos_sale_picking_keep/README.rst | 6 +- pos_sale_picking_keep/__manifest__.py | 1 + pos_sale_picking_keep/models/__init__.py | 4 + pos_sale_picking_keep/models/pos_config.py | 12 +++ pos_sale_picking_keep/models/pos_order.py | 14 ---- .../models/pos_order_line.py | 26 ++++++ .../models/res_config_settings.py | 12 +++ .../models/sale_order_line.py | 43 +++++++--- pos_sale_picking_keep/models/stock_picking.py | 27 +++++++ .../static/description/index.html | 24 +++--- .../tests/tours/pos_sale_picking_keep.esm.js | 15 ++++ pos_sale_picking_keep/tests/__init__.py | 1 + pos_sale_picking_keep/tests/common.py | 34 ++++++++ .../tests/test_pos_sale_picking_keep.py | 19 +---- .../test_pos_sale_picking_keep_realtime.py | 79 +++++++++++++++++++ .../views/res_config_settings.xml | 24 ++++++ 16 files changed, 280 insertions(+), 61 deletions(-) create mode 100644 pos_sale_picking_keep/models/pos_config.py create mode 100644 pos_sale_picking_keep/models/pos_order_line.py create mode 100644 pos_sale_picking_keep/models/res_config_settings.py create mode 100644 pos_sale_picking_keep/models/stock_picking.py create mode 100644 pos_sale_picking_keep/tests/common.py create mode 100644 pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py create mode 100644 pos_sale_picking_keep/views/res_config_settings.xml diff --git a/pos_sale_picking_keep/README.rst b/pos_sale_picking_keep/README.rst index fd25b9428f..5818c055bf 100644 --- a/pos_sale_picking_keep/README.rst +++ b/pos_sale_picking_keep/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - =========================== Keep sale pickings from PoS =========================== @@ -17,7 +13,7 @@ Keep sale pickings from PoS .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github diff --git a/pos_sale_picking_keep/__manifest__.py b/pos_sale_picking_keep/__manifest__.py index 5301bfc033..2a56d56222 100644 --- a/pos_sale_picking_keep/__manifest__.py +++ b/pos_sale_picking_keep/__manifest__.py @@ -10,6 +10,7 @@ "license": "AGPL-3", "installable": True, "depends": ["pos_sale"], + "data": ["views/res_config_settings.xml"], "assets": { "web.assets_tests": [ "pos_sale_picking_keep/static/tests/tours/**/*", diff --git a/pos_sale_picking_keep/models/__init__.py b/pos_sale_picking_keep/models/__init__.py index 1315735d03..ed8a4e604f 100644 --- a/pos_sale_picking_keep/models/__init__.py +++ b/pos_sale_picking_keep/models/__init__.py @@ -2,3 +2,7 @@ from . import pos_order from . import pos_session from . import sale_order_line +from . import pos_config +from . import pos_order_line +from . import stock_picking +from . import res_config_settings diff --git a/pos_sale_picking_keep/models/pos_config.py b/pos_sale_picking_keep/models/pos_config.py new file mode 100644 index 0000000000..e459473749 --- /dev/null +++ b/pos_sale_picking_keep/models/pos_config.py @@ -0,0 +1,12 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PosConfig(models.Model): + _inherit = "pos.config" + + keep_picking = fields.Boolean( + help="When loading sale orders in POS, Odoo cancels the sale pickings." + "Change the strategy here.", + ) diff --git a/pos_sale_picking_keep/models/pos_order.py b/pos_sale_picking_keep/models/pos_order.py index 653aab9477..74a12aad48 100644 --- a/pos_sale_picking_keep/models/pos_order.py +++ b/pos_sale_picking_keep/models/pos_order.py @@ -1,7 +1,6 @@ # Copyright 2025 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import api, models -from odoo.tools import config class PosOrder(models.Model): @@ -32,16 +31,3 @@ def sync_from_ui(self, orders): res = super().sync_from_ui(orders) pickings._compute_state() return res - - def _create_order_picking(self): - # Nullify the creation of the pickings at this level - # We cannot use self.env.context.get("test_pos_sale_picking_keep") because - # the tours that run in the tests do not allow that context to be maintained. - # Therefore, we use self.config_id.name. - if ( - config["test_enable"] - and self.config_id.name != "test_pos_sale_picking_keep" - ): - # For not breaking tests of other modules - return super()._create_order_picking() - return True diff --git a/pos_sale_picking_keep/models/pos_order_line.py b/pos_sale_picking_keep/models/pos_order_line.py new file mode 100644 index 0000000000..c6462463f8 --- /dev/null +++ b/pos_sale_picking_keep/models/pos_order_line.py @@ -0,0 +1,26 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models + + +class PosOrderLine(models.Model): + _inherit = "pos.order.line" + + def _launch_stock_rule_from_pos_order_lines(self): + """ + Launch stock rules for pos order lines that are not linked to a sale order + line and when the strategy is to keep both pos and sale order pickings + """ + lines_to_launch = self.filtered( + lambda line: not line.order_id.pos_config.keep_picking + or ( + line.order_id.pos_config.picking_keep_strategy + == "keep_sale_pos_pickings" + and not line.sale_order_line_id + ) + ) + if lines_to_launch: + super( + PosOrderLine, lines_to_launch + )._launch_stock_rule_from_pos_order_lines() + return True diff --git a/pos_sale_picking_keep/models/res_config_settings.py b/pos_sale_picking_keep/models/res_config_settings.py new file mode 100644 index 0000000000..35e2dcb4de --- /dev/null +++ b/pos_sale_picking_keep/models/res_config_settings.py @@ -0,0 +1,12 @@ +# Copyright 2026 ACSONE SA/NV +# 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" + + pos_keep_picking = fields.Boolean( + related="pos_config_id.keep_picking", + readonly=False, + ) diff --git a/pos_sale_picking_keep/models/sale_order_line.py b/pos_sale_picking_keep/models/sale_order_line.py index f44e3837a2..22ba2c5d38 100644 --- a/pos_sale_picking_keep/models/sale_order_line.py +++ b/pos_sale_picking_keep/models/sale_order_line.py @@ -1,19 +1,42 @@ # Copyright 2026 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import models class SaleOrderLine(models.Model): _inherit = "sale.order.line" - # TODO: Delete if merged https://github.com/odoo/odoo/pull/253333 def _compute_qty_delivered(self): - self = self.with_context(from_qty_delivered=True) - return super()._compute_qty_delivered() + res = super()._compute_qty_delivered() - # TODO: Delete if merged https://github.com/odoo/odoo/pull/253333 - @api.model - def _convert_qty(self, sale_line, qty, direction): - if self.env.context.get("from_qty_delivered"): - return 0 - return super()._convert_qty(sale_line=sale_line, qty=qty, direction=direction) + # Mimic what is done at pos_sale level but lowering the quantity + # TODO: Delete if merged https://github.com/odoo/odoo/pull/253333 + def update_qty_delivered_from_pickings(sale_line, pos_lines): + if all( + picking.state == "done" for picking in pos_lines.order_id.picking_ids + ): + sale_line.qty_delivered -= sum( + ( + self._convert_qty(sale_line, pos_line.qty, "p2s") + for pos_line in pos_lines + if sale_line.product_id.type != "service" + ), + 0, + ) + + for sale_line in self: + if sale_line.pos_order_line_ids.order_id.config_id.keep_picking: + pos_lines = sale_line.pos_order_line_ids.filtered( + lambda order_line: order_line.order_id.state + not in ["cancel", "draft"] + ) + update_qty_delivered_from_pickings(sale_line, pos_lines) + + refund_lines = ( + sale_line.pos_order_line_ids.refund_orderline_ids.filtered( + lambda order_line: order_line.order_id.state + not in ["cancel", "draft"] + ) + ) + update_qty_delivered_from_pickings(sale_line, refund_lines) + return res diff --git a/pos_sale_picking_keep/models/stock_picking.py b/pos_sale_picking_keep/models/stock_picking.py new file mode 100644 index 0000000000..5b4fbea475 --- /dev/null +++ b/pos_sale_picking_keep/models/stock_picking.py @@ -0,0 +1,27 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + @api.model + def _create_picking_from_pos_order_lines( + self, location_dest_id, lines, picking_type, partner=False + ): + """ + Avoid cancelling existing pickings and re-launching stock rules from + POS line if linked to a sale order line and with a strategy + to keep sale pickings. + """ + lines_without_create = lines.filtered( + lambda line: line.sale_order_line_id + and line.order_id.config_id.keep_picking + ) + return super()._create_picking_from_pos_order_lines( + location_dest_id=location_dest_id, + lines=(lines - lines_without_create), + picking_type=picking_type, + partner=partner, + ) diff --git a/pos_sale_picking_keep/static/description/index.html b/pos_sale_picking_keep/static/description/index.html index 2226b9650b..953690f8af 100644 --- a/pos_sale_picking_keep/static/description/index.html +++ b/pos_sale_picking_keep/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Keep sale pickings from PoS -
+
+

Keep sale pickings from PoS

- - -Odoo Community Association - -
-

Keep sale pickings from PoS

-

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

This module inhibits the manipulation that the point of sale mades over the sales orders pickings, and the creation of new pickings under the PoS picking type.

@@ -391,7 +386,7 @@

Keep sale pickings from PoS

-

Use Cases / Context

+

Use Cases / Context

In some scenarios, you may not want that the point of sale (PoS) handles the pickings of the products you are paying:

    @@ -401,7 +396,7 @@

    Use Cases / Context

    In that cases, it’s better to keep the original sales pickings.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -409,15 +404,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Tecnativa
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -432,6 +427,5 @@

Maintainers

-
diff --git a/pos_sale_picking_keep/static/tests/tours/pos_sale_picking_keep.esm.js b/pos_sale_picking_keep/static/tests/tours/pos_sale_picking_keep.esm.js index 63ef17f323..2f3fc360c4 100644 --- a/pos_sale_picking_keep/static/tests/tours/pos_sale_picking_keep.esm.js +++ b/pos_sale_picking_keep/static/tests/tours/pos_sale_picking_keep.esm.js @@ -30,3 +30,18 @@ registry.category("web_tour.tours").add("PosSalePickingKeep2", { PaymentScreen.clickValidate(), ].flat(), }); + +registry.category("web_tour.tours").add("PosSalePickingKeepMixed", { + steps: () => + [ + Chrome.startPoS(), + Dialog.confirm("Open Register"), + PosSale.settleNthOrder(1), + ProductScreen.selectedOrderlineHas("Test Product", "1.00"), + ProductScreen.addOrderline("Test Product 2"), + ProductScreen.clickPayButton(), + PaymentScreen.clickPaymentMethod("Bank", true, {remaining: "0.0"}), + PaymentScreen.clickValidate(), + ReceiptScreen.isShown(), + ].flat(), +}); diff --git a/pos_sale_picking_keep/tests/__init__.py b/pos_sale_picking_keep/tests/__init__.py index bdfa733c4b..054f42ef8c 100644 --- a/pos_sale_picking_keep/tests/__init__.py +++ b/pos_sale_picking_keep/tests/__init__.py @@ -1 +1,2 @@ from . import test_pos_sale_picking_keep +from . import test_pos_sale_picking_keep_realtime diff --git a/pos_sale_picking_keep/tests/common.py b/pos_sale_picking_keep/tests/common.py new file mode 100644 index 0000000000..c1de50cbd7 --- /dev/null +++ b/pos_sale_picking_keep/tests/common.py @@ -0,0 +1,34 @@ +# Copyright 2026 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import odoo.tests + +from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon + + +@odoo.tests.tagged("post_install", "-at_install") +class PosSalePickingKeepCommon(TestPointOfSaleHttpCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.main_pos_config.keep_picking = True + cls.customer = cls.env["res.partner"].create({"name": "Test partner"}) + cls.warehouse = cls.env["stock.warehouse"].search( + [("company_id", "=", cls.env.company.id)], limit=1 + ) + cls.product = cls.env["product.product"].create( + { + "name": "Test Product", + "available_in_pos": True, + "is_storable": True, + "lst_price": 10.0, + } + ) + cls.product_2 = cls.env["product.product"].create( + { + "name": "Test Product 2", + "available_in_pos": True, + "is_storable": True, + "lst_price": 10.0, + } + ) + cls.main_pos_config.name = "test_pos_sale_picking_keep" diff --git a/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py index b85b122233..1823c6328f 100644 --- a/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py +++ b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py @@ -1,30 +1,15 @@ # Copyright 2026 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -import odoo.tests from odoo.tests import Form -from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon +from .common import PosSalePickingKeepCommon -@odoo.tests.tagged("post_install", "-at_install") -class TestPosSalePickingKeep(TestPointOfSaleHttpCommon): +class TestPosSalePickingKeep(PosSalePickingKeepCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.env.company.point_of_sale_update_stock_quantities = "closing" - cls.customer = cls.env["res.partner"].create({"name": "Test partner"}) - cls.warehouse = cls.env["stock.warehouse"].search( - [("company_id", "=", cls.env.company.id)], limit=1 - ) - cls.product = cls.env["product.product"].create( - { - "name": "Test Product", - "available_in_pos": True, - "is_storable": True, - "lst_price": 10.0, - } - ) - cls.main_pos_config.name = "test_pos_sale_picking_keep" def test_sale_order_pos_order_done(self): self.env["stock.quant"]._update_available_quantity( diff --git a/pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py new file mode 100644 index 0000000000..3242f538b0 --- /dev/null +++ b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py @@ -0,0 +1,79 @@ +# Copyright 2026 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests import Form + +from .common import PosSalePickingKeepCommon + + +class TestPosSalePickingKeepRealtime(PosSalePickingKeepCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env.company.point_of_sale_update_stock_quantities = "real" + + def test_sale_order_pos_order_done(self): + self.env["stock.quant"]._update_available_quantity( + self.product, self.warehouse.lot_stock_id, 1 + ) + self.env["stock.quant"]._update_available_quantity( + self.product_2, self.warehouse.lot_stock_id, 10 + ) + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.client_order_ref = "test_pos_sale_picking_keep" + with order_form.order_line.new() as line_form: + line_form.product_id = self.product + sale_order = order_form.save() + sol = sale_order.order_line + self.assertEqual(sol.qty_delivered, 0) + self.main_pos_config.open_ui() + self.start_tour( + "/pos/ui?config_id=%d" % self.main_pos_config.id, + "PosSalePickingKeepMixed", + login="accountman", + ) + self.assertEqual(sale_order.state, "sale") + self.assertEqual(len(sale_order.picking_ids), 1) + pos_order = sol.pos_order_line_ids.order_id + self.assertEqual(pos_order.state, "paid") + self.assertTrue(pos_order.picking_ids) + so_picking = sale_order.picking_ids + self.assertEqual(so_picking.state, "assigned") + self.assertEqual(sol.qty_delivered, 0) + sale_order.picking_ids.button_validate() + self.assertEqual(so_picking.state, "done") + self.assertEqual(sol.qty_delivered, 1) + + def test_sale_order_pos_order_done_and_pos_picking(self): + self.env["stock.quant"]._update_available_quantity( + self.product, self.warehouse.lot_stock_id, 1 + ) + self.env["stock.quant"]._update_available_quantity( + self.product_2, self.warehouse.lot_stock_id, 10 + ) + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.client_order_ref = "test_pos_sale_picking_keep" + with order_form.order_line.new() as line_form: + line_form.product_id = self.product + sale_order = order_form.save() + sol = sale_order.order_line + self.assertEqual(sol.qty_delivered, 0) + self.main_pos_config.open_ui() + self.start_tour( + "/pos/ui?config_id=%d" % self.main_pos_config.id, + "PosSalePickingKeepMixed", + login="accountman", + ) + self.assertEqual(sale_order.state, "sale") + self.assertEqual(len(sale_order.picking_ids), 1) + pos_order = sol.pos_order_line_ids.order_id + self.assertEqual(pos_order.state, "paid") + self.assertTrue(pos_order.picking_ids) + self.assertEqual(pos_order.picking_ids.state, "done") + so_picking = sale_order.picking_ids + self.assertEqual(so_picking.state, "assigned") + self.assertEqual(sol.qty_delivered, 0) + sale_order.picking_ids.button_validate() + self.assertEqual(so_picking.state, "done") + self.assertEqual(sol.qty_delivered, 1) diff --git a/pos_sale_picking_keep/views/res_config_settings.xml b/pos_sale_picking_keep/views/res_config_settings.xml new file mode 100644 index 0000000000..d6344e0775 --- /dev/null +++ b/pos_sale_picking_keep/views/res_config_settings.xml @@ -0,0 +1,24 @@ + + + + + res.config.settings.view.form.inherit.pos_sale_picking_keep + res.config.settings + + + + + + + + + + + From d56a2c543920a5829423549807604297aeb0973c Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Tue, 5 May 2026 16:13:08 +0200 Subject: [PATCH 2/3] [IMP] pos_sale_picking_keep: Add contributors --- pos_sale_picking_keep/README.rst | 12 ++++++++++++ pos_sale_picking_keep/readme/CONTRIBUTORS.md | 6 ++++++ .../static/description/index.html | 19 +++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 pos_sale_picking_keep/readme/CONTRIBUTORS.md diff --git a/pos_sale_picking_keep/README.rst b/pos_sale_picking_keep/README.rst index 5818c055bf..f63b76bb51 100644 --- a/pos_sale_picking_keep/README.rst +++ b/pos_sale_picking_keep/README.rst @@ -66,6 +66,18 @@ Authors * Tecnativa +Contributors +------------ + +- `Tecnativa `__ + + - Pedro M. Baeza + - Víctor Martínez + +- `ACSONE SA/NV `__: + + - Denis Roussel + Maintainers ----------- diff --git a/pos_sale_picking_keep/readme/CONTRIBUTORS.md b/pos_sale_picking_keep/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..c0fea7d6e8 --- /dev/null +++ b/pos_sale_picking_keep/readme/CONTRIBUTORS.md @@ -0,0 +1,6 @@ +- [Tecnativa](https://www.tecnativa.com) + - Pedro M. Baeza + - Víctor Martínez + +- [ACSONE SA/NV](https://acsone.eu): + - Denis Roussel \<\> \ No newline at end of file diff --git a/pos_sale_picking_keep/static/description/index.html b/pos_sale_picking_keep/static/description/index.html index 953690f8af..b68419ba91 100644 --- a/pos_sale_picking_keep/static/description/index.html +++ b/pos_sale_picking_keep/static/description/index.html @@ -380,7 +380,8 @@

Keep sale pickings from PoS

  • Bug Tracker
  • Credits
  • @@ -411,8 +412,22 @@

    Authors

  • Tecnativa
  • +
    +

    Contributors

    + +
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association From c8d7817b21290948f8f0dffe18cad74ff432e8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Wed, 24 Jun 2026 13:17:40 +0200 Subject: [PATCH 3/3] [IMP] pos_sale_picking_keep: remove the configuration and related files With this module installed, the expected behavior should apply by default, without any special configuration. Therefore, we removed these settings. TT63397 --- pos_sale_picking_keep/__manifest__.py | 2 +- pos_sale_picking_keep/models/__init__.py | 2 - pos_sale_picking_keep/models/pos_config.py | 12 ------ .../models/pos_order_line.py | 12 +----- .../models/res_config_settings.py | 12 ------ .../models/sale_order_line.py | 43 +++++-------------- pos_sale_picking_keep/models/stock_picking.py | 5 +-- pos_sale_picking_keep/tests/common.py | 1 - .../tests/test_pos_sale_picking_keep.py | 6 +-- .../test_pos_sale_picking_keep_realtime.py | 6 +-- .../views/res_config_settings.xml | 24 ----------- 11 files changed, 18 insertions(+), 107 deletions(-) delete mode 100644 pos_sale_picking_keep/models/pos_config.py delete mode 100644 pos_sale_picking_keep/models/res_config_settings.py delete mode 100644 pos_sale_picking_keep/views/res_config_settings.xml diff --git a/pos_sale_picking_keep/__manifest__.py b/pos_sale_picking_keep/__manifest__.py index 2a56d56222..c8cbaef513 100644 --- a/pos_sale_picking_keep/__manifest__.py +++ b/pos_sale_picking_keep/__manifest__.py @@ -10,7 +10,7 @@ "license": "AGPL-3", "installable": True, "depends": ["pos_sale"], - "data": ["views/res_config_settings.xml"], + "data": [], "assets": { "web.assets_tests": [ "pos_sale_picking_keep/static/tests/tours/**/*", diff --git a/pos_sale_picking_keep/models/__init__.py b/pos_sale_picking_keep/models/__init__.py index ed8a4e604f..d2893a12d2 100644 --- a/pos_sale_picking_keep/models/__init__.py +++ b/pos_sale_picking_keep/models/__init__.py @@ -2,7 +2,5 @@ from . import pos_order from . import pos_session from . import sale_order_line -from . import pos_config from . import pos_order_line from . import stock_picking -from . import res_config_settings diff --git a/pos_sale_picking_keep/models/pos_config.py b/pos_sale_picking_keep/models/pos_config.py deleted file mode 100644 index e459473749..0000000000 --- a/pos_sale_picking_keep/models/pos_config.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright 2026 ACSONE SA/NV -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, models - - -class PosConfig(models.Model): - _inherit = "pos.config" - - keep_picking = fields.Boolean( - help="When loading sale orders in POS, Odoo cancels the sale pickings." - "Change the strategy here.", - ) diff --git a/pos_sale_picking_keep/models/pos_order_line.py b/pos_sale_picking_keep/models/pos_order_line.py index c6462463f8..7fcd3b4832 100644 --- a/pos_sale_picking_keep/models/pos_order_line.py +++ b/pos_sale_picking_keep/models/pos_order_line.py @@ -8,17 +8,9 @@ class PosOrderLine(models.Model): def _launch_stock_rule_from_pos_order_lines(self): """ - Launch stock rules for pos order lines that are not linked to a sale order - line and when the strategy is to keep both pos and sale order pickings + Launch stock rules for pos order lines that are not linked to a sale order line """ - lines_to_launch = self.filtered( - lambda line: not line.order_id.pos_config.keep_picking - or ( - line.order_id.pos_config.picking_keep_strategy - == "keep_sale_pos_pickings" - and not line.sale_order_line_id - ) - ) + lines_to_launch = self.filtered(lambda line: not line.sale_order_line_id) if lines_to_launch: super( PosOrderLine, lines_to_launch diff --git a/pos_sale_picking_keep/models/res_config_settings.py b/pos_sale_picking_keep/models/res_config_settings.py deleted file mode 100644 index 35e2dcb4de..0000000000 --- a/pos_sale_picking_keep/models/res_config_settings.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright 2026 ACSONE SA/NV -# 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" - - pos_keep_picking = fields.Boolean( - related="pos_config_id.keep_picking", - readonly=False, - ) diff --git a/pos_sale_picking_keep/models/sale_order_line.py b/pos_sale_picking_keep/models/sale_order_line.py index 22ba2c5d38..f44e3837a2 100644 --- a/pos_sale_picking_keep/models/sale_order_line.py +++ b/pos_sale_picking_keep/models/sale_order_line.py @@ -1,42 +1,19 @@ # Copyright 2026 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import models +from odoo import api, models class SaleOrderLine(models.Model): _inherit = "sale.order.line" + # TODO: Delete if merged https://github.com/odoo/odoo/pull/253333 def _compute_qty_delivered(self): - res = super()._compute_qty_delivered() + self = self.with_context(from_qty_delivered=True) + return super()._compute_qty_delivered() - # Mimic what is done at pos_sale level but lowering the quantity - # TODO: Delete if merged https://github.com/odoo/odoo/pull/253333 - def update_qty_delivered_from_pickings(sale_line, pos_lines): - if all( - picking.state == "done" for picking in pos_lines.order_id.picking_ids - ): - sale_line.qty_delivered -= sum( - ( - self._convert_qty(sale_line, pos_line.qty, "p2s") - for pos_line in pos_lines - if sale_line.product_id.type != "service" - ), - 0, - ) - - for sale_line in self: - if sale_line.pos_order_line_ids.order_id.config_id.keep_picking: - pos_lines = sale_line.pos_order_line_ids.filtered( - lambda order_line: order_line.order_id.state - not in ["cancel", "draft"] - ) - update_qty_delivered_from_pickings(sale_line, pos_lines) - - refund_lines = ( - sale_line.pos_order_line_ids.refund_orderline_ids.filtered( - lambda order_line: order_line.order_id.state - not in ["cancel", "draft"] - ) - ) - update_qty_delivered_from_pickings(sale_line, refund_lines) - return res + # TODO: Delete if merged https://github.com/odoo/odoo/pull/253333 + @api.model + def _convert_qty(self, sale_line, qty, direction): + if self.env.context.get("from_qty_delivered"): + return 0 + return super()._convert_qty(sale_line=sale_line, qty=qty, direction=direction) diff --git a/pos_sale_picking_keep/models/stock_picking.py b/pos_sale_picking_keep/models/stock_picking.py index 5b4fbea475..8a4a53951e 100644 --- a/pos_sale_picking_keep/models/stock_picking.py +++ b/pos_sale_picking_keep/models/stock_picking.py @@ -15,10 +15,7 @@ def _create_picking_from_pos_order_lines( POS line if linked to a sale order line and with a strategy to keep sale pickings. """ - lines_without_create = lines.filtered( - lambda line: line.sale_order_line_id - and line.order_id.config_id.keep_picking - ) + lines_without_create = lines.filtered(lambda line: line.sale_order_line_id) return super()._create_picking_from_pos_order_lines( location_dest_id=location_dest_id, lines=(lines - lines_without_create), diff --git a/pos_sale_picking_keep/tests/common.py b/pos_sale_picking_keep/tests/common.py index c1de50cbd7..526600a5c2 100644 --- a/pos_sale_picking_keep/tests/common.py +++ b/pos_sale_picking_keep/tests/common.py @@ -10,7 +10,6 @@ class PosSalePickingKeepCommon(TestPointOfSaleHttpCommon): @classmethod def setUpClass(cls): super().setUpClass() - cls.main_pos_config.keep_picking = True cls.customer = cls.env["res.partner"].create({"name": "Test partner"}) cls.warehouse = cls.env["stock.warehouse"].search( [("company_id", "=", cls.env.company.id)], limit=1 diff --git a/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py index 1823c6328f..4ef909cba5 100644 --- a/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py +++ b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep.py @@ -24,8 +24,7 @@ def test_sale_order_pos_order_done(self): sol = sale_order.order_line self.assertEqual(sol.qty_delivered, 0) self.main_pos_config.open_ui() - self.start_tour( - "/pos/ui?config_id=%d" % self.main_pos_config.id, + self.start_pos_tour( "PosSalePickingKeep1", login="accountman", ) @@ -43,8 +42,7 @@ def test_sale_order_pos_order_done(self): def test_pos_order_flow(self): self.main_pos_config.open_ui() - self.start_tour( - "/pos/ui?config_id=%d" % self.main_pos_config.id, + self.start_pos_tour( "PosSalePickingKeep2", login="accountman", ) diff --git a/pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py index 3242f538b0..38e9163b97 100644 --- a/pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py +++ b/pos_sale_picking_keep/tests/test_pos_sale_picking_keep_realtime.py @@ -27,8 +27,7 @@ def test_sale_order_pos_order_done(self): sol = sale_order.order_line self.assertEqual(sol.qty_delivered, 0) self.main_pos_config.open_ui() - self.start_tour( - "/pos/ui?config_id=%d" % self.main_pos_config.id, + self.start_pos_tour( "PosSalePickingKeepMixed", login="accountman", ) @@ -60,8 +59,7 @@ def test_sale_order_pos_order_done_and_pos_picking(self): sol = sale_order.order_line self.assertEqual(sol.qty_delivered, 0) self.main_pos_config.open_ui() - self.start_tour( - "/pos/ui?config_id=%d" % self.main_pos_config.id, + self.start_pos_tour( "PosSalePickingKeepMixed", login="accountman", ) diff --git a/pos_sale_picking_keep/views/res_config_settings.xml b/pos_sale_picking_keep/views/res_config_settings.xml deleted file mode 100644 index d6344e0775..0000000000 --- a/pos_sale_picking_keep/views/res_config_settings.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - res.config.settings.view.form.inherit.pos_sale_picking_keep - res.config.settings - - - - - - - - - - -