diff --git a/setup/stock_picking_propagate_scheduled_date/odoo/addons/stock_picking_propagate_scheduled_date b/setup/stock_picking_propagate_scheduled_date/odoo/addons/stock_picking_propagate_scheduled_date new file mode 120000 index 000000000000..139d76ff7345 --- /dev/null +++ b/setup/stock_picking_propagate_scheduled_date/odoo/addons/stock_picking_propagate_scheduled_date @@ -0,0 +1 @@ +../../../../stock_picking_propagate_scheduled_date \ No newline at end of file diff --git a/setup/stock_picking_propagate_scheduled_date/setup.py b/setup/stock_picking_propagate_scheduled_date/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_picking_propagate_scheduled_date/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_picking_propagate_scheduled_date/README.rst b/stock_picking_propagate_scheduled_date/README.rst new file mode 100644 index 000000000000..47f815a9d291 --- /dev/null +++ b/stock_picking_propagate_scheduled_date/README.rst @@ -0,0 +1,80 @@ +====================================== +Stock Picking Propagate Scheduled Date +====================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b86c76c7b5e1e98b40d4097ad1387344790b739da7982d1a40bef5a9e1800040 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fstock--logistics--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-workflow/tree/15.0/stock_picking_propagate_scheduled_date + :alt: OCA/stock-logistics-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-workflow-15-0/stock-logistics-workflow-15-0-stock_picking_propagate_scheduled_date + :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/stock-logistics-workflow&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Allows changes on the scheduled date of the pickings to be propagated when picking moves are chained. + +Propagate the date delta between the new date and the old date to the ``move_dest_ids`` of the stock move being updated. + +**Table of contents** + +.. contents:: + :local: + +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 +~~~~~~~ + +* Camptocamp SA + +Contributors +~~~~~~~~~~~~ + +* `Camptocamp `_ + + * Vincent Van Rossem + +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/stock-logistics-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_picking_propagate_scheduled_date/__init__.py b/stock_picking_propagate_scheduled_date/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/stock_picking_propagate_scheduled_date/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_picking_propagate_scheduled_date/__manifest__.py b/stock_picking_propagate_scheduled_date/__manifest__.py new file mode 100644 index 000000000000..a4889aa2c14e --- /dev/null +++ b/stock_picking_propagate_scheduled_date/__manifest__.py @@ -0,0 +1,13 @@ +# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). +# @author Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Stock Picking Propagate Scheduled Date", + "summary": "Propagate Stock Picking Scheduled Date", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "author": "Camptocamp SA, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-workflow", + "depends": ["stock"], +} diff --git a/stock_picking_propagate_scheduled_date/models/__init__.py b/stock_picking_propagate_scheduled_date/models/__init__.py new file mode 100644 index 000000000000..6bda2d2428e0 --- /dev/null +++ b/stock_picking_propagate_scheduled_date/models/__init__.py @@ -0,0 +1 @@ +from . import stock_move diff --git a/stock_picking_propagate_scheduled_date/models/stock_move.py b/stock_picking_propagate_scheduled_date/models/stock_move.py new file mode 100644 index 000000000000..b51141388efc --- /dev/null +++ b/stock_picking_propagate_scheduled_date/models/stock_move.py @@ -0,0 +1,33 @@ +# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). +# @author Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + def _propagate_date(self, new_date): + """Propagate the date of a move to its destination.""" + already_propagated_ids = self.env.context.get( + "date_propagation_ids", set() + ) | set(self.ids) + self = self.with_context(date_propagation_ids=already_propagated_ids) + for move in self: + if move.date: + delta = move.date - fields.Datetime.to_datetime(new_date) + else: + delta = 0 + for move_dest in move.move_dest_ids: + if move_dest.state in ("done", "cancel"): + continue + if move_dest.id in already_propagated_ids: + continue + move_dest.date -= delta + + def write(self, vals): + # propagate date changes in the stock move chain + if "date" in vals: + self._propagate_date(vals.get("date")) + return super().write(vals) diff --git a/stock_picking_propagate_scheduled_date/readme/CONTRIBUTORS.rst b/stock_picking_propagate_scheduled_date/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..6cdbd9a20712 --- /dev/null +++ b/stock_picking_propagate_scheduled_date/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Camptocamp `_ + + * Vincent Van Rossem diff --git a/stock_picking_propagate_scheduled_date/readme/DESCRIPTION.rst b/stock_picking_propagate_scheduled_date/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..20ea25c9b381 --- /dev/null +++ b/stock_picking_propagate_scheduled_date/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +Allows changes on the scheduled date of the pickings to be propagated when picking moves are chained. + +Propagate the date delta between the new date and the old date to the ``move_dest_ids`` of the stock move being updated. diff --git a/stock_picking_propagate_scheduled_date/static/description/icon.png b/stock_picking_propagate_scheduled_date/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/stock_picking_propagate_scheduled_date/static/description/icon.png differ diff --git a/stock_picking_propagate_scheduled_date/static/description/index.html b/stock_picking_propagate_scheduled_date/static/description/index.html new file mode 100644 index 000000000000..8933655b4d1d --- /dev/null +++ b/stock_picking_propagate_scheduled_date/static/description/index.html @@ -0,0 +1,428 @@ + + + + + + +Stock Picking Propagate Scheduled Date + + + +
+

Stock Picking Propagate Scheduled Date

+ + +

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

+

Allows changes on the scheduled date of the pickings to be propagated when picking moves are chained.

+

Propagate the date delta between the new date and the old date to the move_dest_ids of the stock move being updated.

+

Table of contents

+ +
+

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

+
    +
  • Camptocamp SA
  • +
+
+
+

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.

+

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

+

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

+
+
+
+ + diff --git a/stock_picking_propagate_scheduled_date/tests/__init__.py b/stock_picking_propagate_scheduled_date/tests/__init__.py new file mode 100644 index 000000000000..31d172a93d61 --- /dev/null +++ b/stock_picking_propagate_scheduled_date/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_picking_propagate_scheduled_date diff --git a/stock_picking_propagate_scheduled_date/tests/test_stock_picking_propagate_scheduled_date.py b/stock_picking_propagate_scheduled_date/tests/test_stock_picking_propagate_scheduled_date.py new file mode 100644 index 000000000000..ecda42fcc973 --- /dev/null +++ b/stock_picking_propagate_scheduled_date/tests/test_stock_picking_propagate_scheduled_date.py @@ -0,0 +1,195 @@ +from datetime import datetime, timedelta + +from odoo.tests.common import TransactionCase + + +class TestStockPickingPropagateScheduledDate(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stock_location = cls.env.ref("stock.stock_location_stock") + cls.output_location = cls.env.ref("stock.stock_location_output") + cls.output_location.active = True + cls.customer_location = cls.env.ref("stock.stock_location_customers") + + # Configure a warehouse with delivery_steps = 'pick_ship' + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.warehouse.delivery_steps = "pick_ship" + + # Create a product + cls.product = cls.env["product.product"].create( + { + "name": "Test Product", + "type": "product", + } + ) + + # Set initial stock + cls.env["stock.quant"]._update_available_quantity( + cls.product, cls.stock_location, 2.0 + ) + # Create a procurement for 3 Units of the product on the Customers location + pg = cls.env["procurement.group"].create({"name": "Procurement"}) + cls.env["procurement.group"].run( + [ + pg.Procurement( + cls.product, # product_id + 3.0, # product_qty + cls.product.uom_id, # product_uom + cls.customer_location, # location_id + "procurement", # name + "procurement", # origin + cls.warehouse.company_id, # company_id + # values + {"warehouse_id": cls.warehouse, "group_id": pg}, + ) + ] + ) + # This will create 2 pickings: 1 for pick, 1 for ship + cls.pick = cls.env["stock.picking"].search( + [ + ("location_id", "=", cls.stock_location.id), + ("location_dest_id", "=", cls.output_location.id), + ("group_id", "=", pg.id), + ] + ) + cls.ship = cls.env["stock.picking"].search( + [ + ("location_id", "=", cls.output_location.id), + ("location_dest_id", "=", cls.customer_location.id), + ("group_id", "=", pg.id), + ] + ) + + def test_01_pick_reschedule(self): + """change the Pick date""" + # Set the scheduled_date of the Pick picking to next week. + new_scheduled_date = datetime.now().replace(microsecond=0) + timedelta(days=7) + self.pick.scheduled_date = new_scheduled_date + + # the date of the stock.move in the Pick picking is moved to next week + self.assertEqual( + self.pick.move_lines[0].date, + new_scheduled_date, + "The date of the stock.move in the Pick picking should be moved to next week", + ) + + # the date of the stock.move in the Ship picking is moved to next week + self.assertEqual( + self.ship.move_lines[0].date, + new_scheduled_date, + "The date of the stock.move in the Ship picking should be moved to next week", + ) + + # the scheduled_date of the Ship picking is moved to next week + self.assertEqual( + self.ship.scheduled_date, + new_scheduled_date, + "The `scheduled_date` of the Ship picking should be moved to next week", + ) + + def test_02_ship_pick_reschedule(self): + """change the Ship date, then the Pick date""" + # Set the scheduled_date of the Ship picking to next week. + new_scheduled_date = datetime.now().replace(microsecond=0) + timedelta(days=7) + self.ship.scheduled_date = new_scheduled_date + + # Then set the scheduled_date of the Pick picking to next week. + self.pick.scheduled_date = new_scheduled_date + + # the date of the stock.move in the Pick picking is moved to next week + self.assertEqual( + self.pick.move_lines[0].date, + new_scheduled_date, + "The date of stock.move in the Pick picking should be moved to next week", + ) + + # the date of the stock.move in the Ship picking is moved to in 2 weeks + self.assertEqual( + self.ship.move_lines[0].date, + new_scheduled_date + timedelta(days=7), + "the date of the stock.move in the Ship picking should be moved to in 2 weeks", + ) + + # the scheduled_date of the Ship picking is moved to in 2 weeks + self.assertEqual( + self.ship.scheduled_date, + new_scheduled_date + timedelta(days=7), + "the scheduled_date of the Ship picking should be moved to in 2 weeks", + ) + + def test_03_ship_pick_backorder_reschedule(self): + """backorder handling : reschedule before process chained""" + # Set the scheduled_date of the Ship picking to next week. + new_scheduled_date = datetime.now().replace(microsecond=0) + timedelta(days=7) + self.ship.scheduled_date = new_scheduled_date + + # Process the picking for the available quantity of the product (2) + self.pick.action_assign() + self.pick.move_lines[0].quantity_done = 2.0 + self.pick._action_done() + + # Create a backorder of Pick. + backorder_pick = self.env["stock.picking"].search( + [("backorder_id", "=", self.pick.id)] + ) + + # Change the scheduled date of the Pick backorder to next week. + backorder_pick.scheduled_date = new_scheduled_date + + # the date of the stock.move in the Ship picking is in 2 weeks + self.assertEqual( + self.ship.move_lines[0].date, + new_scheduled_date + timedelta(days=7), + "the date of the stock.move in the Ship picking should be moved to in 2 weeks", + ) + # the scheduled date of the Ship picking is in 2 weeks + self.assertEqual( + self.ship.scheduled_date, + new_scheduled_date + timedelta(days=7), + "the scheduled_date of the Ship picking should be moved to in 2 weeks", + ) + + def test_04_ship_process_pick_multiple_backorders(self): + """backorder handling : process chained before reschedule""" + # Set the scheduled_date of the Ship picking to next week. + new_scheduled_date = datetime.now().replace(microsecond=0) + timedelta(days=7) + self.ship.scheduled_date = new_scheduled_date + + # Process the picking for the available quantity of the product (2) + self.pick.action_assign() + self.pick.move_lines[0].quantity_done = 2.0 + self.pick._action_done() + + # Create a backorder of Pick. + backorder_pick = self.env["stock.picking"].search( + [("backorder_id", "=", self.pick.id)] + ) + + # Process the Ship picking for the available quantity of the product (2) + self.ship.action_assign() + self.ship.move_lines[0].quantity_done = 2.0 + self.ship._action_done() + + # create a backorder of Ship + backorder_ship = self.env["stock.picking"].search( + [("backorder_id", "=", self.ship.id)] + ) + + # Change the scheduled date of the Pick backorder to next week. + backorder_pick.scheduled_date = new_scheduled_date + + # the date of the stock.move in the Ship backorder picking is in 2 weeks + self.assertEqual( + backorder_ship.move_lines[0].date, + new_scheduled_date + timedelta(days=7), + "the date of the stock.move in the Ship backorder " + "picking should be moved to in 2 weeks", + ) + + # the scheduled date of the Ship backorder picking is in 2 weeks + self.assertEqual( + backorder_ship.move_lines[0].date, + new_scheduled_date + timedelta(days=7), + "the scheduled_date of the Ship picking backorder should be moved to in 2 weeks", + )