From 19f1b9db33d4e567c81f45c0e90554ead78979a7 Mon Sep 17 00:00:00 2001 From: Michael Tietz Date: Fri, 28 Jul 2023 15:28:49 +0200 Subject: [PATCH 1/4] [ADD] sale_stock_prebook: Add process to prebook a sale order's stock before confirming it --- sale_stock_prebook/README.rst | 93 ++++ sale_stock_prebook/__init__.py | 1 + sale_stock_prebook/__manifest__.py | 18 + sale_stock_prebook/models/__init__.py | 5 + .../models/report_stock_forecasted.py | 13 + sale_stock_prebook/models/sale.py | 110 +++++ .../models/stock_location_route.py | 12 + sale_stock_prebook/models/stock_move.py | 13 + sale_stock_prebook/models/stock_rule.py | 21 + sale_stock_prebook/readme/CONTRIBUTORS.rst | 1 + sale_stock_prebook/readme/DESCRIPTION.rst | 1 + sale_stock_prebook/readme/USAGE.rst | 4 + .../static/description/index.html | 430 ++++++++++++++++++ sale_stock_prebook/tests/__init__.py | 1 + sale_stock_prebook/tests/common.py | 47 ++ .../tests/test_sale_stock_prebook.py | 51 +++ sale_stock_prebook/views/sale_views.xml | 24 + .../views/stock_location_route_views.xml | 16 + .../odoo/addons/sale_stock_prebook | 1 + setup/sale_stock_prebook/setup.py | 6 + 20 files changed, 868 insertions(+) create mode 100644 sale_stock_prebook/README.rst create mode 100644 sale_stock_prebook/__init__.py create mode 100644 sale_stock_prebook/__manifest__.py create mode 100644 sale_stock_prebook/models/__init__.py create mode 100644 sale_stock_prebook/models/report_stock_forecasted.py create mode 100644 sale_stock_prebook/models/sale.py create mode 100644 sale_stock_prebook/models/stock_location_route.py create mode 100644 sale_stock_prebook/models/stock_move.py create mode 100644 sale_stock_prebook/models/stock_rule.py create mode 100644 sale_stock_prebook/readme/CONTRIBUTORS.rst create mode 100644 sale_stock_prebook/readme/DESCRIPTION.rst create mode 100644 sale_stock_prebook/readme/USAGE.rst create mode 100644 sale_stock_prebook/static/description/index.html create mode 100644 sale_stock_prebook/tests/__init__.py create mode 100644 sale_stock_prebook/tests/common.py create mode 100644 sale_stock_prebook/tests/test_sale_stock_prebook.py create mode 100644 sale_stock_prebook/views/sale_views.xml create mode 100644 sale_stock_prebook/views/stock_location_route_views.xml create mode 120000 setup/sale_stock_prebook/odoo/addons/sale_stock_prebook create mode 100644 setup/sale_stock_prebook/setup.py diff --git a/sale_stock_prebook/README.rst b/sale_stock_prebook/README.rst new file mode 100644 index 00000000000..c6a955bf03f --- /dev/null +++ b/sale_stock_prebook/README.rst @@ -0,0 +1,93 @@ +================== +sale_stock_prebook +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:5b9cb364c10d40c3c7dc3e952ef0b8de29ef9049481266dd5638c240d3d3cd94 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/sale-workflow/tree/14.0/sale_stock_prebook + :alt: OCA/sale-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/sale-workflow-14-0/sale-workflow-14-0-sale_stock_prebook + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Add process to prebook a sale order's stock before confirming it + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +On the sale.order view you will find two new buttons "Reserve Stock" and "Release reservation" +which are only shown if the state is in "Quotation" or "Quotation Sent". + +The reservation creates the picking/moves via a procurement run by placing a real move. + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* MT Software +* BCIM + +Contributors +~~~~~~~~~~~~ + +* Michael Tietz (MT Software) + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-mt-software-de| image:: https://github.com/mt-software-de.png?size=40px + :target: https://github.com/mt-software-de + :alt: mt-software-de + +Current `maintainer `__: + +|maintainer-mt-software-de| + +This module is part of the `OCA/sale-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_stock_prebook/__init__.py b/sale_stock_prebook/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/sale_stock_prebook/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_stock_prebook/__manifest__.py b/sale_stock_prebook/__manifest__.py new file mode 100644 index 00000000000..57f16aed2b1 --- /dev/null +++ b/sale_stock_prebook/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2023 Michael Tietz (MT Software) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +{ + "name": "sale_stock_prebook", + "summary": "Add process to prebook a sale order's stock before confirming it", + "version": "14.0.1.0.0", + "author": "MT Software, BCIM, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/sale-workflow", + "data": [ + "views/sale_views.xml", + "views/stock_location_route_views.xml", + ], + "depends": [ + "sale_stock", + ], + "maintainers": ["mt-software-de"], + "license": "LGPL-3", +} diff --git a/sale_stock_prebook/models/__init__.py b/sale_stock_prebook/models/__init__.py new file mode 100644 index 00000000000..5e0c95565d5 --- /dev/null +++ b/sale_stock_prebook/models/__init__.py @@ -0,0 +1,5 @@ +from . import sale +from . import stock_move +from . import stock_rule +from . import report_stock_forecasted +from . import stock_location_route diff --git a/sale_stock_prebook/models/report_stock_forecasted.py b/sale_stock_prebook/models/report_stock_forecasted.py new file mode 100644 index 00000000000..e42e50c8f1b --- /dev/null +++ b/sale_stock_prebook/models/report_stock_forecasted.py @@ -0,0 +1,13 @@ +# Copyright 2023 Michael Tietz (MT Software) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo import models +from odoo.osv import expression + + +class ReplenishmentReport(models.AbstractModel): + _inherit = "report.stock.report_product_product_replenishment" + + def _product_sale_domain(self, product_template_ids, product_variant_ids): + domain = super()._product_sale_domain(product_template_ids, product_variant_ids) + domain = expression.AND([domain, [("order_id.stock_is_reserved", "=", False)]]) + return domain diff --git a/sale_stock_prebook/models/sale.py b/sale_stock_prebook/models/sale.py new file mode 100644 index 00000000000..cc18e7c2086 --- /dev/null +++ b/sale_stock_prebook/models/sale.py @@ -0,0 +1,110 @@ +# Copyright 2023 Michael Tietz (MT Software) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def _prepare_reserve_procurement_values(self, group_id=None): + values = self._prepare_procurement_values(group_id) + values["used_for_sale_reservation"] = True + return values + + def _should_prebook_stock(self): + """Checks if SOL product has no_sale_stock_prebook set + to know if we need to reserve it or not""" + self.ensure_one() + for route in self.product_id.route_ids: + if route.no_sale_stock_prebook: + return False + return True + + def _prepare_reserve_procurement(self, group): + """Adjusts UOM qty for product, makes list of field values for + procurement group""" + product_qty, procurement_uom = self.product_uom._adjust_uom_quantities( + self.product_uom_qty, self.product_id.uom_id + ) + return self.env["procurement.group"].Procurement( + self.product_id, + product_qty, + procurement_uom, + self.order_id.partner_shipping_id.property_stock_customer, + self.product_id.display_name, + group.name, + self.order_id.company_id, + self._prepare_reserve_procurement_values(group_id=group), + ) + + def _prepare_reserve_procurements(self, group): + """Prepares list of dicts - reserve procurements""" + procurements = [] + for line in self: + if not line._should_prebook_stock(): + continue + procurements.append(line._prepare_reserve_procurement(group)) + return procurements + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + stock_is_reserved = fields.Boolean( + "Stock is reserved", + compute="_compute_stock_is_reserved", + store=True, + ) + + def _get_reservation_pickings(self): + return self.picking_ids.filtered( + lambda p: any(m.used_for_sale_reservation for m in p.move_lines) + ) + + @api.depends("picking_ids.move_lines.used_for_sale_reservation") + def _compute_stock_is_reserved(self): + for rec in self: + rec.stock_is_reserved = (rec._get_reservation_pickings() and True) or False + + def _action_cancel(self): + self.release_reservation() + return super()._action_cancel() + + def _action_confirm(self): + self.release_reservation() + return super()._action_confirm() + + def _prepare_reserve_procurement_group_values(self): + self.ensure_one() + values = self.order_line[0]._prepare_procurement_group_vals() + values["name"] = f"Reservation for {values['name']}" + return values + + def _create_reserve_procurement_group(self): + return self.env["procurement.group"].create( + self._prepare_reserve_procurement_group_values() + ) + + def reserve_stock(self): + self = self.filtered( + lambda s: not s.stock_is_reserved + and s.state in ["draft", "sent"] + or not s.order_line + ) + if not self: + return + + self = self.with_context(sale_stock_prebook_stop_proc_run=True) + procurements = [] + + for order in self: + group = order._create_reserve_procurement_group() + procurements += order.order_line._prepare_reserve_procurements(group) + if procurements: + self.env["procurement.group"].run(procurements) + + def release_reservation(self): + pickings = self._get_reservation_pickings() + pickings.action_cancel() + pickings.group_id.sudo().unlink() + pickings.sudo().unlink() diff --git a/sale_stock_prebook/models/stock_location_route.py b/sale_stock_prebook/models/stock_location_route.py new file mode 100644 index 00000000000..c95798edbfe --- /dev/null +++ b/sale_stock_prebook/models/stock_location_route.py @@ -0,0 +1,12 @@ +# Copyright 2023 Raumschmiede GmbH +from odoo import fields, models + + +class Route(models.Model): + _inherit = "stock.location.route" + + no_sale_stock_prebook = fields.Boolean( + string="No Sale Stock Prebook", + help="If set no stock will be prebooked, " + "for configured Products with this route", + ) diff --git a/sale_stock_prebook/models/stock_move.py b/sale_stock_prebook/models/stock_move.py new file mode 100644 index 00000000000..be51f87dfb7 --- /dev/null +++ b/sale_stock_prebook/models/stock_move.py @@ -0,0 +1,13 @@ +# Copyright 2023 Michael Tietz (MT Software) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo import fields, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + used_for_sale_reservation = fields.Boolean(default=False) + + def _action_assign(self): + self = self.filtered(lambda m: not m.used_for_sale_reservation) + return super(StockMove, self)._action_assign() diff --git a/sale_stock_prebook/models/stock_rule.py b/sale_stock_prebook/models/stock_rule.py new file mode 100644 index 00000000000..07cfd5d1ae9 --- /dev/null +++ b/sale_stock_prebook/models/stock_rule.py @@ -0,0 +1,21 @@ +# Copyright 2023 Michael Tietz (MT Software) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo import models + + +class StockRule(models.Model): + _inherit = "stock.rule" + + def _get_custom_move_fields(self): + res = super()._get_custom_move_fields() + res.append("used_for_sale_reservation") + return res + + def _run_pull(self, procurements): + if not self.env.context.get("sale_stock_prebook_stop_proc_run"): + return super()._run_pull(procurements) + actions_to_run = [] + for procurement, rule in procurements: + if rule.picking_type_id.code == "outgoing": + actions_to_run.append((procurement, rule)) + super()._run_pull(actions_to_run) diff --git a/sale_stock_prebook/readme/CONTRIBUTORS.rst b/sale_stock_prebook/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..446fd1d5769 --- /dev/null +++ b/sale_stock_prebook/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Michael Tietz (MT Software) diff --git a/sale_stock_prebook/readme/DESCRIPTION.rst b/sale_stock_prebook/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..5cf4f0938a5 --- /dev/null +++ b/sale_stock_prebook/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Add process to prebook a sale order's stock before confirming it diff --git a/sale_stock_prebook/readme/USAGE.rst b/sale_stock_prebook/readme/USAGE.rst new file mode 100644 index 00000000000..585cd4de2e5 --- /dev/null +++ b/sale_stock_prebook/readme/USAGE.rst @@ -0,0 +1,4 @@ +On the sale.order view you will find two new buttons "Reserve Stock" and "Release reservation" +which are only shown if the state is in "Quotation" or "Quotation Sent". + +The reservation creates the picking/moves via a procurement run by placing a real move. diff --git a/sale_stock_prebook/static/description/index.html b/sale_stock_prebook/static/description/index.html new file mode 100644 index 00000000000..0101c168eb0 --- /dev/null +++ b/sale_stock_prebook/static/description/index.html @@ -0,0 +1,430 @@ + + + + + +sale_stock_prebook + + + +
+

sale_stock_prebook

+ + +

Beta License: LGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

+

Add process to prebook a sale order’s stock before confirming it

+

Table of contents

+ +
+

Usage

+

On the sale.order view you will find two new buttons “Reserve Stock” and “Release reservation” +which are only shown if the state is in “Quotation” or “Quotation Sent”.

+

The reservation creates the picking/moves via a procurement run by placing a real move.

+
+
+

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 +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • MT Software
  • +
  • BCIM
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

mt-software-de

+

This module is part of the OCA/sale-workflow project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/sale_stock_prebook/tests/__init__.py b/sale_stock_prebook/tests/__init__.py new file mode 100644 index 00000000000..20cffeae611 --- /dev/null +++ b/sale_stock_prebook/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_stock_prebook diff --git a/sale_stock_prebook/tests/common.py b/sale_stock_prebook/tests/common.py new file mode 100644 index 00000000000..ed8aa43970a --- /dev/null +++ b/sale_stock_prebook/tests/common.py @@ -0,0 +1,47 @@ +# Copyright 2023 Michael Tietz (MT Software) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo.tests import Form, common + + +class TestSaleStockPrebookCase(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + partner_form = Form(cls.env["res.partner"]) + partner_form.name = "Test partner" + cls.partner = partner_form.save() + + no_prebook_stock_route_id = ( + cls.env["stock.location.route"] + .sudo() + .create( + { + "name": "Test Route Without Prebook Stock", + "no_sale_stock_prebook": True, + } + ) + ) + # prebook product + product_form = Form(cls.env["product.product"]) + product_form.name = "Test Product 1" + product_form.type = "product" + cls.product_1 = product_form.save() + # non-prebook product + product_form = Form(cls.env["product.product"]) + product_form.name = "Test Product 22" + product_form.type = "product" + product_form.route_ids.add(no_prebook_stock_route_id) + cls.product_2 = product_form.save() + + cls.sale = cls.create_sale_order([(cls.product_1, 3), (cls.product_2, 3)]) + cls.sale2 = cls.create_sale_order([(cls.product_2, 3)]) + + @classmethod + def create_sale_order(cls, products): + sale_order_form = Form(cls.env["sale.order"]) + sale_order_form.partner_id = cls.partner + for product, qty in products: + with sale_order_form.order_line.new() as order_line_form: + order_line_form.product_id = product + order_line_form.product_uom_qty = qty + return sale_order_form.save() diff --git a/sale_stock_prebook/tests/test_sale_stock_prebook.py b/sale_stock_prebook/tests/test_sale_stock_prebook.py new file mode 100644 index 00000000000..e377cc612f0 --- /dev/null +++ b/sale_stock_prebook/tests/test_sale_stock_prebook.py @@ -0,0 +1,51 @@ +# Copyright 2023 Michael Tietz (MT Software) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo.exceptions import UserError +from odoo.tests import tagged + +from .common import TestSaleStockPrebookCase + + +@tagged("post_install", "-at_install") +class TestStockReserveSale(TestSaleStockPrebookCase): + def test_10_reserve_and_release(self): + self.sale.reserve_stock() + self.sale2.reserve_stock() + self.assertTrue(self.sale.stock_is_reserved) + self.assertFalse(self.sale2.stock_is_reserved) + reservation_pickings = self.sale._get_reservation_pickings() + self.assertEqual( + len(reservation_pickings), + 1, + "There should be one reservation picking created", + ) + self.assertEqual( + len(self.sale.picking_ids), 1, "There should be only one picking created" + ) + self.assertEqual(self.sale.picking_ids.move_lines.product_id, self.product_1) + self.assertFalse(self.sale2.picking_ids) + self.sale.release_reservation() + reservation_pickings = self.sale._get_reservation_pickings() + self.assertFalse(self.sale.stock_is_reserved) + self.assertEqual( + len(reservation_pickings), 0, "There should be no reservation picking" + ) + self.assertEqual(len(self.sale.picking_ids), 0, "There should be no picking") + + def test_20_confirmation_release(self): + self.sale.reserve_stock() + self.sale.action_confirm() + self.assertFalse(self.sale.stock_is_reserved) + + def test_30_cancelation_release(self): + self.sale.reserve_stock() + self.sale.action_cancel() + self.assertFalse(self.sale.stock_is_reserved) + + def test_40_action_assign(self): + self.sale.reserve_stock() + self.sale.picking_ids.move_lines._action_assign() + self.assertEqual(self.sale.picking_ids.move_lines.state, "confirmed") + self.assertFalse(self.sale.picking_ids.move_lines.move_line_ids) + with self.assertRaises(UserError): + self.sale.picking_ids.button_validate() diff --git a/sale_stock_prebook/views/sale_views.xml b/sale_stock_prebook/views/sale_views.xml new file mode 100644 index 00000000000..d4d9ea223d2 --- /dev/null +++ b/sale_stock_prebook/views/sale_views.xml @@ -0,0 +1,24 @@ + + + sale.order.form.reserve.simple + sale.order + + + + + + diff --git a/sale_stock_prebook/views/stock_location_route_views.xml b/sale_stock_prebook/views/stock_location_route_views.xml new file mode 100644 index 00000000000..d39e67282ec --- /dev/null +++ b/sale_stock_prebook/views/stock_location_route_views.xml @@ -0,0 +1,16 @@ + + + + stock.location.route.form + + stock.location.route + + + + + + + diff --git a/setup/sale_stock_prebook/odoo/addons/sale_stock_prebook b/setup/sale_stock_prebook/odoo/addons/sale_stock_prebook new file mode 120000 index 00000000000..19613e66825 --- /dev/null +++ b/setup/sale_stock_prebook/odoo/addons/sale_stock_prebook @@ -0,0 +1 @@ +../../../../sale_stock_prebook \ No newline at end of file diff --git a/setup/sale_stock_prebook/setup.py b/setup/sale_stock_prebook/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/sale_stock_prebook/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 5585c0957bfc0aa093ae1781c09baf0edbae0e48 Mon Sep 17 00:00:00 2001 From: "Laurent Mignonn (ACSONE)" Date: Mon, 25 Nov 2024 11:04:32 +0100 Subject: [PATCH 2/4] [MIG] sale_stock_prebook: Migration from 16.0 --- sale_stock_prebook/README.rst | 10 +++++----- sale_stock_prebook/__manifest__.py | 4 ++-- sale_stock_prebook/models/__init__.py | 2 +- sale_stock_prebook/models/sale.py | 4 ++-- .../{stock_location_route.py => stock_route.py} | 5 ++--- .../static/description/index.html | 17 ++++++++++------- sale_stock_prebook/tests/common.py | 8 ++++---- .../tests/test_sale_stock_prebook.py | 8 ++++---- ...on_route_views.xml => stock_route_views.xml} | 6 +++--- 9 files changed, 33 insertions(+), 31 deletions(-) rename sale_stock_prebook/models/{stock_location_route.py => stock_route.py} (67%) rename sale_stock_prebook/views/{stock_location_route_views.xml => stock_route_views.xml} (69%) diff --git a/sale_stock_prebook/README.rst b/sale_stock_prebook/README.rst index c6a955bf03f..0d9048792f8 100644 --- a/sale_stock_prebook/README.rst +++ b/sale_stock_prebook/README.rst @@ -17,13 +17,13 @@ sale_stock_prebook :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github - :target: https://github.com/OCA/sale-workflow/tree/14.0/sale_stock_prebook + :target: https://github.com/OCA/sale-workflow/tree/16.0/sale_stock_prebook :alt: OCA/sale-workflow .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/sale-workflow-14-0/sale-workflow-14-0-sale_stock_prebook + :target: https://translation.odoo-community.org/projects/sale-workflow-16-0/sale-workflow-16-0-sale_stock_prebook :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=14.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=16.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -49,7 +49,7 @@ 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -88,6 +88,6 @@ Current `maintainer `__: |maintainer-mt-software-de| -This module is part of the `OCA/sale-workflow `_ project on GitHub. +This module is part of the `OCA/sale-workflow `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_stock_prebook/__manifest__.py b/sale_stock_prebook/__manifest__.py index 57f16aed2b1..95fecc4f746 100644 --- a/sale_stock_prebook/__manifest__.py +++ b/sale_stock_prebook/__manifest__.py @@ -3,12 +3,12 @@ { "name": "sale_stock_prebook", "summary": "Add process to prebook a sale order's stock before confirming it", - "version": "14.0.1.0.0", + "version": "16.0.1.0.0", "author": "MT Software, BCIM, Odoo Community Association (OCA)", "website": "https://github.com/OCA/sale-workflow", "data": [ "views/sale_views.xml", - "views/stock_location_route_views.xml", + "views/stock_route_views.xml", ], "depends": [ "sale_stock", diff --git a/sale_stock_prebook/models/__init__.py b/sale_stock_prebook/models/__init__.py index 5e0c95565d5..6c10e033b2d 100644 --- a/sale_stock_prebook/models/__init__.py +++ b/sale_stock_prebook/models/__init__.py @@ -2,4 +2,4 @@ from . import stock_move from . import stock_rule from . import report_stock_forecasted -from . import stock_location_route +from . import stock_route diff --git a/sale_stock_prebook/models/sale.py b/sale_stock_prebook/models/sale.py index cc18e7c2086..9246be9c4fb 100644 --- a/sale_stock_prebook/models/sale.py +++ b/sale_stock_prebook/models/sale.py @@ -58,10 +58,10 @@ class SaleOrder(models.Model): def _get_reservation_pickings(self): return self.picking_ids.filtered( - lambda p: any(m.used_for_sale_reservation for m in p.move_lines) + lambda p: any(m.used_for_sale_reservation for m in p.move_ids) ) - @api.depends("picking_ids.move_lines.used_for_sale_reservation") + @api.depends("picking_ids.move_ids.used_for_sale_reservation") def _compute_stock_is_reserved(self): for rec in self: rec.stock_is_reserved = (rec._get_reservation_pickings() and True) or False diff --git a/sale_stock_prebook/models/stock_location_route.py b/sale_stock_prebook/models/stock_route.py similarity index 67% rename from sale_stock_prebook/models/stock_location_route.py rename to sale_stock_prebook/models/stock_route.py index c95798edbfe..4ee64476a00 100644 --- a/sale_stock_prebook/models/stock_location_route.py +++ b/sale_stock_prebook/models/stock_route.py @@ -2,11 +2,10 @@ from odoo import fields, models -class Route(models.Model): - _inherit = "stock.location.route" +class StockRoute(models.Model): + _inherit = "stock.route" no_sale_stock_prebook = fields.Boolean( - string="No Sale Stock Prebook", help="If set no stock will be prebooked, " "for configured Products with this route", ) diff --git a/sale_stock_prebook/static/description/index.html b/sale_stock_prebook/static/description/index.html index 0101c168eb0..5972d359549 100644 --- a/sale_stock_prebook/static/description/index.html +++ b/sale_stock_prebook/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -368,7 +369,7 @@

sale_stock_prebook

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:5b9cb364c10d40c3c7dc3e952ef0b8de29ef9049481266dd5638c240d3d3cd94 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: LGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

+

Beta License: LGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

Add process to prebook a sale order’s stock before confirming it

Table of contents

@@ -394,7 +395,7 @@

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 -feedback.

+feedback.

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

@@ -415,13 +416,15 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

Current maintainer:

mt-software-de

-

This module is part of the OCA/sale-workflow project on GitHub.

+

This module is part of the OCA/sale-workflow project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/sale_stock_prebook/tests/common.py b/sale_stock_prebook/tests/common.py index ed8aa43970a..d850a96764c 100644 --- a/sale_stock_prebook/tests/common.py +++ b/sale_stock_prebook/tests/common.py @@ -3,7 +3,7 @@ from odoo.tests import Form, common -class TestSaleStockPrebookCase(common.SavepointCase): +class TestSaleStockPrebookCase(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -12,7 +12,7 @@ def setUpClass(cls): cls.partner = partner_form.save() no_prebook_stock_route_id = ( - cls.env["stock.location.route"] + cls.env["stock.route"] .sudo() .create( { @@ -24,12 +24,12 @@ def setUpClass(cls): # prebook product product_form = Form(cls.env["product.product"]) product_form.name = "Test Product 1" - product_form.type = "product" + product_form.detailed_type = "product" cls.product_1 = product_form.save() # non-prebook product product_form = Form(cls.env["product.product"]) product_form.name = "Test Product 22" - product_form.type = "product" + product_form.detailed_type = "product" product_form.route_ids.add(no_prebook_stock_route_id) cls.product_2 = product_form.save() diff --git a/sale_stock_prebook/tests/test_sale_stock_prebook.py b/sale_stock_prebook/tests/test_sale_stock_prebook.py index e377cc612f0..e2e645862c8 100644 --- a/sale_stock_prebook/tests/test_sale_stock_prebook.py +++ b/sale_stock_prebook/tests/test_sale_stock_prebook.py @@ -22,7 +22,7 @@ def test_10_reserve_and_release(self): self.assertEqual( len(self.sale.picking_ids), 1, "There should be only one picking created" ) - self.assertEqual(self.sale.picking_ids.move_lines.product_id, self.product_1) + self.assertEqual(self.sale.picking_ids.move_ids.product_id, self.product_1) self.assertFalse(self.sale2.picking_ids) self.sale.release_reservation() reservation_pickings = self.sale._get_reservation_pickings() @@ -44,8 +44,8 @@ def test_30_cancelation_release(self): def test_40_action_assign(self): self.sale.reserve_stock() - self.sale.picking_ids.move_lines._action_assign() - self.assertEqual(self.sale.picking_ids.move_lines.state, "confirmed") - self.assertFalse(self.sale.picking_ids.move_lines.move_line_ids) + self.sale.picking_ids.move_ids._action_assign() + self.assertEqual(self.sale.picking_ids.move_ids.state, "confirmed") + self.assertFalse(self.sale.picking_ids.move_ids.move_line_ids) with self.assertRaises(UserError): self.sale.picking_ids.button_validate() diff --git a/sale_stock_prebook/views/stock_location_route_views.xml b/sale_stock_prebook/views/stock_route_views.xml similarity index 69% rename from sale_stock_prebook/views/stock_location_route_views.xml rename to sale_stock_prebook/views/stock_route_views.xml index d39e67282ec..b38505ee01c 100644 --- a/sale_stock_prebook/views/stock_location_route_views.xml +++ b/sale_stock_prebook/views/stock_route_views.xml @@ -1,12 +1,12 @@ - stock.location.route.form + stock.route.form - stock.location.route + stock.route From f1d88465cce50189d1e0d7376eff2a5e6f752bfc Mon Sep 17 00:00:00 2001 From: "Laurent Mignonn (ACSONE)" Date: Mon, 25 Nov 2024 11:24:45 +0100 Subject: [PATCH 3/4] [IMP] sale_stock_prebook: Prevent qty done on reservation move --- sale_stock_prebook/models/stock_move.py | 17 ++++++++++++++--- .../tests/test_sale_stock_prebook.py | 7 ++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/sale_stock_prebook/models/stock_move.py b/sale_stock_prebook/models/stock_move.py index be51f87dfb7..cd5b5bc0718 100644 --- a/sale_stock_prebook/models/stock_move.py +++ b/sale_stock_prebook/models/stock_move.py @@ -1,6 +1,7 @@ # Copyright 2023 Michael Tietz (MT Software) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class StockMove(models.Model): @@ -8,6 +9,16 @@ class StockMove(models.Model): used_for_sale_reservation = fields.Boolean(default=False) - def _action_assign(self): + @api.constrains("used_for_sale_reservation", "quantity_done") + def _check_used_for_sale_reservation(self): + for move in self: + if move.used_for_sale_reservation and move.quantity_done: + raise ValidationError( + _( + "You cannot set a quantity done on a move used for sale reservation" + ) + ) + + def _action_assign(self, force_qty=None): self = self.filtered(lambda m: not m.used_for_sale_reservation) - return super(StockMove, self)._action_assign() + return super(StockMove, self)._action_assign(force_qty=force_qty) diff --git a/sale_stock_prebook/tests/test_sale_stock_prebook.py b/sale_stock_prebook/tests/test_sale_stock_prebook.py index e2e645862c8..eef16486223 100644 --- a/sale_stock_prebook/tests/test_sale_stock_prebook.py +++ b/sale_stock_prebook/tests/test_sale_stock_prebook.py @@ -1,6 +1,6 @@ # Copyright 2023 Michael Tietz (MT Software) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError from odoo.tests import tagged from .common import TestSaleStockPrebookCase @@ -49,3 +49,8 @@ def test_40_action_assign(self): self.assertFalse(self.sale.picking_ids.move_ids.move_line_ids) with self.assertRaises(UserError): self.sale.picking_ids.button_validate() + + def test_50_process_move(self): + self.sale.reserve_stock() + with self.assertRaisesRegex(ValidationError, "You cannot set a quantity done"): + self.sale.picking_ids.move_ids.quantity_done = 3 From 72b79ca1dce7e0c3824e796839eaaa7cac103ebd Mon Sep 17 00:00:00 2001 From: "Laurent Mignonn (ACSONE)" Date: Mon, 25 Nov 2024 12:04:04 +0100 Subject: [PATCH 4/4] [FIX] sale_stock_prebook: Fix tests when sale_cancel_reason is installed --- sale_stock_prebook/tests/common.py | 7 +++++++ sale_stock_prebook/tests/test_sale_stock_prebook.py | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sale_stock_prebook/tests/common.py b/sale_stock_prebook/tests/common.py index d850a96764c..1e741912727 100644 --- a/sale_stock_prebook/tests/common.py +++ b/sale_stock_prebook/tests/common.py @@ -2,11 +2,18 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). from odoo.tests import Form, common +from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT + class TestSaleStockPrebookCase(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, disable_cancel_warning=True, **DISABLED_MAIL_CONTEXT + ) + ) partner_form = Form(cls.env["res.partner"]) partner_form.name = "Test partner" cls.partner = partner_form.save() diff --git a/sale_stock_prebook/tests/test_sale_stock_prebook.py b/sale_stock_prebook/tests/test_sale_stock_prebook.py index eef16486223..d2f31de7215 100644 --- a/sale_stock_prebook/tests/test_sale_stock_prebook.py +++ b/sale_stock_prebook/tests/test_sale_stock_prebook.py @@ -1,12 +1,10 @@ # Copyright 2023 Michael Tietz (MT Software) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). from odoo.exceptions import UserError, ValidationError -from odoo.tests import tagged from .common import TestSaleStockPrebookCase -@tagged("post_install", "-at_install") class TestStockReserveSale(TestSaleStockPrebookCase): def test_10_reserve_and_release(self): self.sale.reserve_stock()