From c62a65bcbbf05b2bbdead7936fa4a5777f7e6d85 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Fri, 10 Jan 2025 10:02:48 +0100 Subject: [PATCH 1/2] [ADD] sale_order_blanket_order_stock_prebook_release: Manage move priority on blanket orders When a call-off order is confirmed for a product, the priority date used to compute the quantity available at promised date must be the start date of the blanket order. This is required to ensure that the principle of first-come, first-served is respected. In the case of a product part of a blanket order the date to consider must be the start date of the blanket order and not the date of the call-off order. --- .../README.rst | 97 ++++ .../__init__.py | 2 + .../__manifest__.py | 18 + .../hooks.py | 33 ++ .../models/__init__.py | 2 + .../models/sale_order.py | 62 +++ .../models/sale_order_line.py | 19 + .../readme/CONTEXT.md | 4 + .../readme/CONTRIBUTORS.md | 1 + .../readme/CREDITS.md | 3 + .../readme/DESCRIPTION.md | 1 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 444 ++++++++++++++++++ .../tests/__init__.py | 0 .../tests/common.py | 40 ++ ...der_blanket_order_stock_prebook_release.py | 170 +++++++ ..._order_blanket_order_stock_prebook_release | 1 + .../setup.py | 6 + 18 files changed, 903 insertions(+) create mode 100644 sale_order_blanket_order_stock_prebook_release/README.rst create mode 100644 sale_order_blanket_order_stock_prebook_release/__init__.py create mode 100644 sale_order_blanket_order_stock_prebook_release/__manifest__.py create mode 100644 sale_order_blanket_order_stock_prebook_release/hooks.py create mode 100644 sale_order_blanket_order_stock_prebook_release/models/__init__.py create mode 100644 sale_order_blanket_order_stock_prebook_release/models/sale_order.py create mode 100644 sale_order_blanket_order_stock_prebook_release/models/sale_order_line.py create mode 100644 sale_order_blanket_order_stock_prebook_release/readme/CONTEXT.md create mode 100644 sale_order_blanket_order_stock_prebook_release/readme/CONTRIBUTORS.md create mode 100644 sale_order_blanket_order_stock_prebook_release/readme/CREDITS.md create mode 100644 sale_order_blanket_order_stock_prebook_release/readme/DESCRIPTION.md create mode 100644 sale_order_blanket_order_stock_prebook_release/static/description/icon.png create mode 100644 sale_order_blanket_order_stock_prebook_release/static/description/index.html create mode 100644 sale_order_blanket_order_stock_prebook_release/tests/__init__.py create mode 100644 sale_order_blanket_order_stock_prebook_release/tests/common.py create mode 100644 sale_order_blanket_order_stock_prebook_release/tests/test_sale_order_blanket_order_stock_prebook_release.py create mode 120000 setup/sale_order_blanket_order_stock_prebook_release/odoo/addons/sale_order_blanket_order_stock_prebook_release create mode 100644 setup/sale_order_blanket_order_stock_prebook_release/setup.py diff --git a/sale_order_blanket_order_stock_prebook_release/README.rst b/sale_order_blanket_order_stock_prebook_release/README.rst new file mode 100644 index 00000000000..f99aa79acbf --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/README.rst @@ -0,0 +1,97 @@ +============================================== +Sale Order Blanket Order Stock Prebook Release +============================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3f68147daefc9ef868a5b6ba780abc44482618234844943c4d62564d0ba1351d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-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%2Fsale--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/sale-workflow/tree/16.0/sale_order_blanket_order_stock_prebook_release + :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-16-0/sale-workflow-16-0-sale_order_blanket_order_stock_prebook_release + :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=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is a glue module between +sale_order_blanket_order_stock_prebook and +stock_available_to_promise_release + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +When a call-off order is confirmed for a product, the priority date used +to compute the quantity available at promised date must be the start +date of the blanket order. + +This is required to ensure that the principle of first-come, +first-served is respected. In the case of a product part of a blanket +order the date to consider must be the start date of the blanket order +and not the date of the call-off order. + +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 +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Laurent Mignon laurent.mignon@acsone.eu (https://www.acsone.eu) + +Other credits +------------- + +The development of this module has been financially supported by: + +- ALCYON Belux + +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. + +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_order_blanket_order_stock_prebook_release/__init__.py b/sale_order_blanket_order_stock_prebook_release/__init__.py new file mode 100644 index 00000000000..cc6b6354ad8 --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/sale_order_blanket_order_stock_prebook_release/__manifest__.py b/sale_order_blanket_order_stock_prebook_release/__manifest__.py new file mode 100644 index 00000000000..508c533bcc8 --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Sale Order Blanket Order Stock Prebook Release", + "summary": """Ensure that the date priotity when releasing""" + """ qty is the start date of the blanker order""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/sale-workflow", + "depends": [ + "sale_order_blanket_order_stock_prebook", + "stock_available_to_promise_release", + ], + "auto_install": True, + "post_init_hook": "post_init_hook", +} diff --git a/sale_order_blanket_order_stock_prebook_release/hooks.py b/sale_order_blanket_order_stock_prebook_release/hooks.py new file mode 100644 index 00000000000..8ba3f695ba0 --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/hooks.py @@ -0,0 +1,33 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + + +def post_init_hook(cr, registry): + """Create `account.payment.method` records + for the installed payment providers. + """ + _logger.info( + "Executing post init hook for module " + "sale_order_blanket_order_stock_prebook_release" + ) + env = api.Environment(cr, SUPERUSER_ID, {}) + blanket_orders = env["sale.order"].search( + [("order_type", "=", "blanket"), ("state", "in", ["sale", "done"])] + ) + + _logger.info( + f"Found {len(blanket_orders)} blanket orders to compute the move date priority" + ) + blanket_orders._compute_blanket_move_date_priority() + + _logger.info("Setting the move date priority for the blanket orders move lines") + for move_id in blanket_orders.order_line.move_ids: + if move_id.state not in ("done", "cancel", "assigned"): + move_id.date_priority = ( + move_id.sale_line_id.order_id.blanket_move_date_priority + ) diff --git a/sale_order_blanket_order_stock_prebook_release/models/__init__.py b/sale_order_blanket_order_stock_prebook_release/models/__init__.py new file mode 100644 index 00000000000..e7e9273fcff --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/models/__init__.py @@ -0,0 +1,2 @@ +from . import sale_order_line +from . import sale_order diff --git a/sale_order_blanket_order_stock_prebook_release/models/sale_order.py b/sale_order_blanket_order_stock_prebook_release/models/sale_order.py new file mode 100644 index 00000000000..94de3b4bd6b --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/models/sale_order.py @@ -0,0 +1,62 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from datetime import timedelta + +from odoo import fields, models + + +class SaleOrder(models.Model): + + _inherit = "sale.order" + + blanket_move_date_priority = fields.Datetime( + string="Move Date Priority", + help="Date priority for the moves of the order.", + ) + + def action_confirm(self): + self.flush_recordset() + self._compute_blanket_move_date_priority() + return super().action_confirm() + + def _compute_blanket_move_date_priority(self): + """ + Compute the move date priority for the blanket orders. + + The move date priority for blanket orders is the validity start date of + the blanket order incremented by the position of the order according to + the confirmation date by blanket_validity_start_date. + This method is called at the start of the confirmation process. + """ + blankets = self.filtered(lambda o: o.order_type == "blanket") + # we need to query the count of confirmed blanket orders for each + # blanket_validity_start_date + if not blankets: + return + + # we must use plain SQL to avoid the transformation of date and datetime + # fields to strings done by the read_group method which is designed to + # be use to display data in a view... :-( + query = """ + SELECT + blanket_validity_start_date, + count(1) + FROM + sale_order + WHERE + order_type = 'blanket' + AND state in ('sale', 'done') + AND blanket_validity_start_date in %s + GROUP BY blanket_validity_start_date + """ + self.env.cr.execute( + query, (tuple(blankets.mapped("blanket_validity_start_date")),) + ) + count_per_date = dict(self.env.cr.fetchall()) + for order in blankets.sorted("create_date"): + start_date = order.blanket_validity_start_date + order_position = count_per_date.get(start_date, 0) + order.blanket_move_date_priority = fields.Datetime.to_datetime( + start_date + ) + timedelta(seconds=order_position) + count_per_date[start_date] = order_position + 1 diff --git a/sale_order_blanket_order_stock_prebook_release/models/sale_order_line.py b/sale_order_blanket_order_stock_prebook_release/models/sale_order_line.py new file mode 100644 index 00000000000..ce45df723d1 --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/models/sale_order_line.py @@ -0,0 +1,19 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class SaleOrderLine(models.Model): + + _inherit = "sale.order.line" + + def _prepare_procurement_values(self, group_id=False): + values = super()._prepare_procurement_values(group_id) + order = self.order_id + if ( + order.order_type == "blanket" + and order.blanket_reservation_strategy == "at_confirm" + ): + values["date_priority"] = order.blanket_move_date_priority + return values diff --git a/sale_order_blanket_order_stock_prebook_release/readme/CONTEXT.md b/sale_order_blanket_order_stock_prebook_release/readme/CONTEXT.md new file mode 100644 index 00000000000..efccea6fe3f --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/readme/CONTEXT.md @@ -0,0 +1,4 @@ +When a call-off order is confirmed for a product, the priority date used to compute the quantity available at promised date must be the start date of the blanket order. + +This is required to ensure that the principle of first-come, first-served is respected. +In the case of a product part of a blanket order the date to consider must be the start date of the blanket order and not the date of the call-off order. \ No newline at end of file diff --git a/sale_order_blanket_order_stock_prebook_release/readme/CONTRIBUTORS.md b/sale_order_blanket_order_stock_prebook_release/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..a15e3ef244f --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Laurent Mignon (https://www.acsone.eu) diff --git a/sale_order_blanket_order_stock_prebook_release/readme/CREDITS.md b/sale_order_blanket_order_stock_prebook_release/readme/CREDITS.md new file mode 100644 index 00000000000..e7c5a535490 --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- ALCYON Belux diff --git a/sale_order_blanket_order_stock_prebook_release/readme/DESCRIPTION.md b/sale_order_blanket_order_stock_prebook_release/readme/DESCRIPTION.md new file mode 100644 index 00000000000..7ac9081b222 --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module is a glue module between sale_order_blanket_order_stock_prebook and stock_available_to_promise_release \ No newline at end of file diff --git a/sale_order_blanket_order_stock_prebook_release/static/description/icon.png b/sale_order_blanket_order_stock_prebook_release/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/sale_order_blanket_order_stock_prebook_release/static/description/index.html b/sale_order_blanket_order_stock_prebook_release/static/description/index.html new file mode 100644 index 00000000000..f0624c2ef98 --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/static/description/index.html @@ -0,0 +1,444 @@ + + + + + +Sale Order Blanket Order Stock Prebook Release + + + +
+

Sale Order Blanket Order Stock Prebook Release

+ + +

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

+

This module is a glue module between +sale_order_blanket_order_stock_prebook and +stock_available_to_promise_release

+

Table of contents

+ +
+

Use Cases / Context

+

When a call-off order is confirmed for a product, the priority date used +to compute the quantity available at promised date must be the start +date of the blanket order.

+

This is required to ensure that the principle of first-come, +first-served is respected. In the case of a product part of a blanket +order the date to consider must be the start date of the blanket order +and not the date of the call-off order.

+
+
+

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

+
    +
  • ACSONE SA/NV
  • +
+
+ +
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • ALCYON Belux
  • +
+
+
+

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.

+

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_order_blanket_order_stock_prebook_release/tests/__init__.py b/sale_order_blanket_order_stock_prebook_release/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sale_order_blanket_order_stock_prebook_release/tests/common.py b/sale_order_blanket_order_stock_prebook_release/tests/common.py new file mode 100644 index 00000000000..175ed4a507a --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/tests/common.py @@ -0,0 +1,40 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from odoo import Command, fields + +from odoo.addons.stock_available_to_promise_release.tests import common + + +class SaleOrderBlanketOrderStockPrebookReleaseCase(common.PromiseReleaseCommonCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.wh.delivery_route_id.write({"available_to_promise_defer_pull": True}) + + cls.blanket_so = cls.env["sale.order"].create( + { + "order_type": "blanket", + "partner_id": cls.partner_delta.id, + "blanket_validity_start_date": "2025-01-01", + "blanket_validity_end_date": "2025-12-31", + "blanket_reservation_strategy": "at_confirm", + "order_line": [ + Command.create( + { + "product_id": cls.product1.id, + "product_uom_qty": 10.0, + "price_unit": 100.0, + } + ), + ], + } + ) + + def _date_to_datetime(self, date, nb_seconds=0): + dt = fields.Datetime.to_datetime(date) + if dt and nb_seconds: + dt += timedelta(seconds=nb_seconds) + return dt diff --git a/sale_order_blanket_order_stock_prebook_release/tests/test_sale_order_blanket_order_stock_prebook_release.py b/sale_order_blanket_order_stock_prebook_release/tests/test_sale_order_blanket_order_stock_prebook_release.py new file mode 100644 index 00000000000..f755033b28e --- /dev/null +++ b/sale_order_blanket_order_stock_prebook_release/tests/test_sale_order_blanket_order_stock_prebook_release.py @@ -0,0 +1,170 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import datetime + +import freezegun + +from odoo import Command + +from .common import SaleOrderBlanketOrderStockPrebookReleaseCase + + +class TestSaleOrderBlanketOrderStockPrebookRelease( + SaleOrderBlanketOrderStockPrebookReleaseCase +): + def test_blanket_move_date_priority(self): + self.assertFalse(self.blanket_so.blanket_move_date_priority) + + # the move date priority is computed when the blanket order is confirmed + self.blanket_so.action_confirm() + self.assertTrue(self.blanket_so.blanket_move_date_priority) + # the move date priority must be the validity start date of the blanket order + # incremented by the x seconds where x is the number of confirmed blanket orders + # with the same validity start date + # At this point we have only one confirmed blanket order with the same validity + # start date + move_date_priority = self.blanket_so.blanket_move_date_priority + self.assertEqual( + move_date_priority, + self._date_to_datetime( + self.blanket_so.blanket_validity_start_date, nb_seconds=0 + ), + ) + # if we create a new blanket order with the same validity start date and confirm it + # the move date priority of the first blanket order must be incremented by 1 second + new_blanket_so = self.env["sale.order"].create( + { + "order_type": "blanket", + "partner_id": self.partner_delta.id, + "blanket_validity_start_date": "2025-01-01", + "blanket_validity_end_date": "2025-12-31", + "blanket_reservation_strategy": "at_confirm", + "order_line": [ + ( + 0, + 0, + { + "product_id": self.product2.id, + "product_uom_qty": 10.0, + "price_unit": 100.0, + }, + ), + ], + } + ) + new_blanket_so.action_confirm() + self.assertEqual( + new_blanket_so.blanket_move_date_priority, + self._date_to_datetime( + new_blanket_so.blanket_validity_start_date, nb_seconds=1 + ), + ) + + # if we create a new blanket order with a different validity start date + # the move date priority of the first blanket order must be the validity start date + # incremented by 0 seconds + new_blanket_so = self.env["sale.order"].create( + { + "order_type": "blanket", + "partner_id": self.partner_delta.id, + "blanket_validity_start_date": "2026-01-01", + "blanket_validity_end_date": "2026-12-31", + "blanket_reservation_strategy": "at_confirm", + "order_line": [ + ( + 0, + 0, + { + "product_id": self.product2.id, + "product_uom_qty": 10.0, + "price_unit": 100.0, + }, + ), + ], + } + ) + new_blanket_so.action_confirm() + self.assertEqual( + new_blanket_so.blanket_move_date_priority, + self._date_to_datetime( + new_blanket_so.blanket_validity_start_date, nb_seconds=0 + ), + ) + + def test_date_priority_on_prebook_moves(self): + """For blanket oders, the prebook moves must have the date priority set to the blanket + move date priority""" + self.blanket_so.action_confirm() + prebook_moves = self.blanket_so.order_line.move_ids.filtered( + "used_for_sale_reservation" + ) + self.assertTrue(prebook_moves) + for move in prebook_moves: + self.assertEqual( + move.date_priority, self.blanket_so.blanket_move_date_priority + ) + + def test_date_priority_on_prebook_moves_2(self): + """For normal order, in case of prebooking, the date priority must be the datetime at + confirmation""" + new_so = self.env["sale.order"].create( + { + "partner_id": self.partner_delta.id, + "order_line": [ + ( + 0, + 0, + { + "product_id": self.product1.id, + "product_uom_qty": 10.0, + "price_unit": 100.0, + }, + ), + ], + } + ) + with freezegun.freeze_time("2020-01-01 00:00:00"): + new_so.reserve_stock() + now = datetime(2020, 1, 1, 0, 0, 0) + prebook_moves = new_so.order_line.move_ids.filtered( + "used_for_sale_reservation" + ) + self.assertTrue(prebook_moves) + for move in prebook_moves: + self.assertEqual(move.date_priority, now) + + def test_date_priority_on_preparation_moves(self): + """For blanket oders, the preparation moves must have the date priority set to the + blanket move date priority""" + self.blanket_so.action_confirm() + + with freezegun.freeze_time("2025-02-01"): + order = self.env["sale.order"].create( + { + "order_type": "call_off", + "partner_id": self.partner_delta.id, + "blanket_order_id": self.blanket_so.id, + "order_line": [ + Command.create( + { + "product_id": self.product1.id, + "product_uom_qty": 10.0, + } + ), + ], + } + ) + order.action_confirm() + + # the date_priority on the moves linked to the blanket order + # for the preparation must be blanket_move_date_priority + self.assertTrue( + order.order_line.blanket_move_ids.filtered( + lambda m: not m.used_for_sale_reservation + ) + ) + for move in order.order_line.blanket_move_ids: + self.assertEqual( + move.date_priority, self.blanket_so.blanket_move_date_priority + ) diff --git a/setup/sale_order_blanket_order_stock_prebook_release/odoo/addons/sale_order_blanket_order_stock_prebook_release b/setup/sale_order_blanket_order_stock_prebook_release/odoo/addons/sale_order_blanket_order_stock_prebook_release new file mode 120000 index 00000000000..de6de0116d8 --- /dev/null +++ b/setup/sale_order_blanket_order_stock_prebook_release/odoo/addons/sale_order_blanket_order_stock_prebook_release @@ -0,0 +1 @@ +../../../../sale_order_blanket_order_stock_prebook_release \ No newline at end of file diff --git a/setup/sale_order_blanket_order_stock_prebook_release/setup.py b/setup/sale_order_blanket_order_stock_prebook_release/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/sale_order_blanket_order_stock_prebook_release/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 54bfbaa0cb39771ddaa3ff04dc0d0b4669e1c3b3 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Fri, 10 Jan 2025 10:03:48 +0100 Subject: [PATCH 2/2] [DO NOT MERGE] tests depenencies --- .github/workflows/test.yml | 13 ++++++++++--- test-requirements.txt | 5 +++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94169c43a55..195185dbb96 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,10 +43,17 @@ jobs: name: test with OCB makepot: "true" - container: ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest - exclude: "sale_packaging_default,sale_order_product_recommendation,sale_order_product_recommendation_packaging_default,sale_order_product_recommendation_elaboration" + exclude: "sale_packaging_default,sale_order_product_recommendation,sale_order_product_recommendation_packaging_default,sale_order_product_recommendation_elaboration,sale_order_blanket_order,sale_order_blanket_order_stock_prebook,sale_order_blanket_order_stock_prebook_release" name: test with Odoo - container: ghcr.io/oca/oca-ci/py3.10-ocb16.0:latest - exclude: "sale_packaging_default,sale_order_product_recommendation,sale_order_product_recommendation_packaging_default,sale_order_product_recommendation_elaboration" + exclude: "sale_packaging_default,sale_order_product_recommendation,sale_order_product_recommendation_packaging_default,sale_order_product_recommendation_elaboration,sale_order_blanket_order,sale_order_blanket_order_stock_prebook,sale_order_blanket_order_stock_prebook_release" + name: test with OCB + makepot: "true" + - container: ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest + include: "sale_order_blanket_order,sale_order_blanket_order_stock_prebook,sale_order_blanket_order_stock_prebook_release" + name: test with Odoo + - container: ghcr.io/oca/oca-ci/py3.10-ocb16.0:latest + include: "sale_order_blanket_order,sale_order_blanket_order_stock_prebook,sale_order_blanket_order_stock_prebook_release" name: test with OCB makepot: "true" services: @@ -80,4 +87,4 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} - name: Update .pot files run: oca_export_and_push_pot https://x-access-token:${{ secrets.GIT_PUSH_TOKEN }}@github.com/${{ github.repository }} - if: ${{ matrix.makepot == 'true' && github.event_name == 'push' && github.repository_owner == 'OCA' }} + if: ${{ matrix.makepot == 'true' && github.event_name == 'push' && github.repository_owner == 'OCA' }} \ No newline at end of file diff --git a/test-requirements.txt b/test-requirements.txt index 66bc2cbae3f..1bb4eafefa2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,6 @@ odoo_test_helper +odoo-addon-sale-stock-prebook @ git+https://github.com/OCA/sale-workflow.git@refs/pull/3481/head#subdirectory=setup/sale_stock_prebook +odoo-addon-sale-stock-prebook-stock-available-to_promise-release @ git+https://github.com/OCA/sale-workflow.git@refs/pull/3424/head#subdirectory=setup/sale_stock_prebook_stock_available_to_promise_release +odoo-addon-sale-order-blanket-order @ git+https://github.com/OCA/sale-workflow.git@refs/pull/3436/head#subdirectory=setup/sale_order_blanket_order +odoo-addon-sale-order-blanket-order-stock-prebook @ git+https://github.com/OCA/sale-workflow.git@refs/pull/3436/head#subdirectory=setup/sale_order_blanket_order_stock_prebook +odoo-addon-sale-order-product-picker @ git+https://github.com/OCA/sale-workflow.git@refs/pull/3457/head#subdirectory=setup/sale_order_product_picker