diff --git a/setup/stock_move_not_merge_by_dest_moves/odoo/addons/stock_move_not_merge_by_dest_moves b/setup/stock_move_not_merge_by_dest_moves/odoo/addons/stock_move_not_merge_by_dest_moves new file mode 120000 index 000000000000..f9374173e644 --- /dev/null +++ b/setup/stock_move_not_merge_by_dest_moves/odoo/addons/stock_move_not_merge_by_dest_moves @@ -0,0 +1 @@ +../../../../stock_move_not_merge_by_dest_moves \ No newline at end of file diff --git a/setup/stock_move_not_merge_by_dest_moves/setup.py b/setup/stock_move_not_merge_by_dest_moves/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_move_not_merge_by_dest_moves/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_move_not_merge_by_dest_moves/README.rst b/stock_move_not_merge_by_dest_moves/README.rst new file mode 100644 index 000000000000..cea6686a64f6 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/README.rst @@ -0,0 +1,232 @@ +============================================== +Stock Move - Do not merge by destination moves +============================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:a311da309386ceedb823109235a3fa327387cb289077f53dc442bcd2262d179f + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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%2Fstock--logistics--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-workflow/tree/16.0/stock_move_not_merge_by_dest_moves + :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-16-0/stock-logistics-workflow-16-0-stock_move_not_merge_by_dest_moves + :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=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of stock move merging to allow you +to never merge stock moves if those moves will go to different +destination moves. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +This module was developed because sometimes we don't want our stock +movements to be merged if goes to different moves because: + +- Sale free products that must be invoiced separatelly (for example, get + 2 + 1 free) +- Use warehouse with 2 or 3 steps in Outgoing Shipments +- Use products with Invoicing Policy: Delivered Quantities + +All of these conditions must be met for this module to be really useful. + +Use case example: +----------------- + +We have an order with two lines of the same product that need to be +weighed (kg) and the offer is "get 2 + 1 free". + +Each piece of Fish measures aproximately 1kg and we assume you know how +many Fishes you need to take. + +The lines of the sale would be like this: + +- 2 pieces of Fish for aproximately 2kg in total. +- 1 piece of Fish for aproximately 1kg in total with 100% discount. + +Odoo Core VS. Module: Workflow comparison +----------------------------------------- + ++----------------------------------+----------------------------------+ +| **Odoo Core** | **With this module** | ++==================================+==================================+ +| The OUT step will not be | The OUT step will not be | +| grouped, so we will have 2 | grouped, so we will have 2 | +| moves. | moves. | ++----------------------------------+----------------------------------+ +| | | ++----------------------------------+----------------------------------+ +| The PICK step will be grouped | Since we have 2 separate moves | +| into one line, telling you that | on the OUT step, we don't want | +| 3 kgs must be demanded | to merge moves in the PICK step. | +| | PICK step will tell you that | +| | 3kgs must be demanded into 2 | +| | separate moves. | ++----------------------------------+----------------------------------+ +| | | ++----------------------------------+----------------------------------+ +| When you measure the 3 fishes in | When you measure the 3 fishes in | +| the PICK step, we get 1.9kg for | the PICK step, we get 1.9kg for | +| the 2 Fishes and 0.7kg for the | the 2 Fishes and 0.7kg for the | +| free Fish. 2.6kg in total. | free Fish. 2.6kg in total. | ++----------------------------------+----------------------------------+ +| | | ++----------------------------------+----------------------------------+ +| Confirm the PICK step. When you | Confirm the PICK step. When you | +| reserve quantities on OUTGOING | reserve quantities on OUTGOING | +| step, 2kg will go to the 2 | step, 1.9kg will go to the 2 | +| Fishes and 0.6kg to the free | Fishes and 0.7kg to the free | +| Fish. | Fish. | ++----------------------------------+----------------------------------+ +| | | ++----------------------------------+----------------------------------+ +| Your invoice to the customer | Your invoice to the customer | +| will be 2kg for the 2 pieces and | will be 1.9kg for the 2 pieces | +| 0.6kg for the free fish. | and 0.7kg for the free fish. | ++----------------------------------+----------------------------------+ +| | | ++----------------------------------+----------------------------------+ +| This is not correct: The 2 pices | This is correct | +| of Fish should be invoiced for | | +| 1.9kg and the free fish should | | +| be invoiced for 0.7kg at 100% | | +| discount. | | ++----------------------------------+----------------------------------+ + +If you also don't want to have to reweigh in the last step if you +exceeded the quantity demanded (Fishes weight 1.14kg each for example), +you might be interested in this module: + +- stock_rule_reserve_max_quantity + +Usage +===== + +To use this module, you need to have activated sales module: + +1. Activate sale module. +2. Go to Warehouse and activate 2 steps shipping process. +3. Create and confirm a sale with 2 lines: One with discount and one + without discount with the same product. +4. Confirm the sale. +5. Check the first picking (PICK) moves is not grouped. +6. Set quantity done on each line that differs (below) the reserved + quantity on PICK picking and confirm it. +7. Go to the OUT picking, and check how quantities are distributed + correctly. + +Known issues / Roadmap +====================== + +- Receipts (second step quantities not correct) + + Symptom: After testing receipts, the quantities shown/propagated in + the second step are not correct (they do not match what was processed + in the first step, or the distribution per move is inconsistent). + + Source: Video “Moduon - Review Gelo [16.0][ADD] + stock_move_not_merge_by_dest_moves #2014”. + https://www.loom.com/share/416b1efb65d04c5d80acd9fac2bf4e0f?sid=8084079a-d28b-4b04-9839-cf3544cddc42 + + Expected: The second-step document should reflect the quantities done + in the first step, line by line / per destination move, without over- + or under-allocation. Actual: The second-step quantities differ from + the first step and/or are misallocated. Status: Under investigation. + +- Sales (SO line changes not updated on picking) + + Symptom: After confirming a Sales Order, changes made on the SO line + are not propagated to the generated picking. + + Source: Video “Moduon - Review Gelo [16.0][ADD] + stock_move_not_merge_by_dest_moves #2014 sales”. + https://www.loom.com/share/8dfe51327d6d435f8dbdcef4af9ce77b?sid=424232ef-c392-4903-8d42-0254da24c812 + + Expected: Updates on the SO line (e.g., quantity/discount that affect + downstream moves) should be reflected on the generated picking. + Actual: The picking does not refresh after SO line changes. Status: + Under investigation. + +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 +------- + +* Moduon + +Contributors +------------ + +- Eduardo de Miguel (`Moduon `__) +- Rafael Blasco (`Moduon `__) + +Other credits +------------- + +The development of this module has been financially supported by: + +- Ulzama + +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-Shide| image:: https://github.com/Shide.png?size=40px + :target: https://github.com/Shide + :alt: Shide +.. |maintainer-rafaelbn| image:: https://github.com/rafaelbn.png?size=40px + :target: https://github.com/rafaelbn + :alt: rafaelbn + +Current `maintainers `__: + +|maintainer-Shide| |maintainer-rafaelbn| + +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_move_not_merge_by_dest_moves/__init__.py b/stock_move_not_merge_by_dest_moves/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_move_not_merge_by_dest_moves/__manifest__.py b/stock_move_not_merge_by_dest_moves/__manifest__.py new file mode 100644 index 000000000000..73faf288375a --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +{ + "name": "Stock Move - Do not merge by destination moves", + "summary": "Do not merge stock moves that go to different destination moves", + "version": "16.0.1.0.0", + "development_status": "Alpha", + "category": "Inventory/Inventory", + "website": "https://github.com/OCA/stock-logistics-workflow", + "author": "Moduon, Odoo Community Association (OCA)", + "maintainers": ["Shide", "rafaelbn"], + "license": "LGPL-3", + "application": False, + "installable": True, + "depends": [ + "sale_stock", + ], +} diff --git a/stock_move_not_merge_by_dest_moves/models/__init__.py b/stock_move_not_merge_by_dest_moves/models/__init__.py new file mode 100644 index 000000000000..6bda2d2428e0 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/models/__init__.py @@ -0,0 +1 @@ +from . import stock_move diff --git a/stock_move_not_merge_by_dest_moves/models/stock_move.py b/stock_move_not_merge_by_dest_moves/models/stock_move.py new file mode 100644 index 000000000000..cd3cb3128397 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/models/stock_move.py @@ -0,0 +1,22 @@ +# Copyright 2025 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +from odoo import api, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + @api.model + def _prepare_merge_moves_distinct_fields(self): + """Do not merge moves that goes to different destination moves""" + fields = super()._prepare_merge_moves_distinct_fields() + fields.append("move_dest_ids") + return fields + + @api.model + def _prepare_merge_negative_moves_excluded_distinct_fields(self): + """Merge negative moves that goes to same destination moves""" + fields = super()._prepare_merge_negative_moves_excluded_distinct_fields() + fields.append("move_dest_ids") + return fields diff --git a/stock_move_not_merge_by_dest_moves/readme/CONTEXT.md b/stock_move_not_merge_by_dest_moves/readme/CONTEXT.md new file mode 100644 index 000000000000..1bb0144a0097 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/readme/CONTEXT.md @@ -0,0 +1,36 @@ +This module was developed because sometimes we don't want our stock movements to be merged if goes to different moves because: +- Sale free products that must be invoiced separatelly (for example, get 2 + 1 free) +- Use warehouse with 2 or 3 steps in Outgoing Shipments +- Use products with Invoicing Policy: Delivered Quantities + +All of these conditions must be met for this module to be really useful. + +## Use case example: + +We have an order with two lines of the same product that need to be weighed (kg) and the offer is "get 2 + 1 free". + +Each piece of Fish measures aproximately 1kg and we assume you know how many Fishes you need to take. + +The lines of the sale would be like this: +- 2 pieces of Fish for aproximately 2kg in total. +- 1 piece of Fish for aproximately 1kg in total with 100% discount. + +## Odoo Core VS. Module: Workflow comparison + +| **Odoo Core** | **With this module** | +|---|---| +| The OUT step will not be grouped, so we will have 2 moves. | The OUT step will not be grouped, so we will have 2 moves. | +| | | +| The PICK step will be grouped into one line, telling you that 3 kgs must be demanded | Since we have 2 separate moves on the OUT step, we don't want to merge moves in the PICK step. PICK step will tell you that 3kgs must be demanded into 2 separate moves. | +| | | +| When you measure the 3 fishes in the PICK step, we get 1.9kg for the 2 Fishes and 0.7kg for the free Fish. 2.6kg in total. | When you measure the 3 fishes in the PICK step, we get 1.9kg for the 2 Fishes and 0.7kg for the free Fish. 2.6kg in total. | +| | | +| Confirm the PICK step. When you reserve quantities on OUTGOING step, 2kg will go to the 2 Fishes and 0.6kg to the free Fish. | Confirm the PICK step. When you reserve quantities on OUTGOING step, 1.9kg will go to the 2 Fishes and 0.7kg to the free Fish. | +| | | +| Your invoice to the customer will be 2kg for the 2 pieces and 0.6kg for the free fish. | Your invoice to the customer will be 1.9kg for the 2 pieces and 0.7kg for the free fish. | +| | | +| This is not correct: The 2 pices of Fish should be invoiced for 1.9kg and the free fish should be invoiced for 0.7kg at 100% discount. | This is correct | + + +If you also don't want to have to reweigh in the last step if you exceeded the quantity demanded (Fishes weight 1.14kg each for example), you might be interested in this module: + - stock_rule_reserve_max_quantity diff --git a/stock_move_not_merge_by_dest_moves/readme/CONTRIBUTORS.md b/stock_move_not_merge_by_dest_moves/readme/CONTRIBUTORS.md new file mode 100644 index 000000000000..0ca1be35b55c --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Eduardo de Miguel ([Moduon](https://www.moduon.team/)) +- Rafael Blasco ([Moduon](https://www.moduon.team/)) diff --git a/stock_move_not_merge_by_dest_moves/readme/CREDITS.md b/stock_move_not_merge_by_dest_moves/readme/CREDITS.md new file mode 100644 index 000000000000..56fb93304346 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- Ulzama diff --git a/stock_move_not_merge_by_dest_moves/readme/DESCRIPTION.md b/stock_move_not_merge_by_dest_moves/readme/DESCRIPTION.md new file mode 100644 index 000000000000..bb8fcbcb3d93 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module extends the functionality of stock move merging to allow you to never merge stock moves if those moves will go to different destination moves. + diff --git a/stock_move_not_merge_by_dest_moves/readme/ROADMAP.md b/stock_move_not_merge_by_dest_moves/readme/ROADMAP.md new file mode 100644 index 000000000000..5e8696c8ad1d --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/readme/ROADMAP.md @@ -0,0 +1,21 @@ +- Receipts (second step quantities not correct) + + Symptom: After testing receipts, the quantities shown/propagated in the second step are not correct (they do not match what was processed in the first step, or the distribution per move is inconsistent). + + Source: Video “Moduon - Review Gelo [16.0][ADD] stock_move_not_merge_by_dest_moves #2014”. + https://www.loom.com/share/416b1efb65d04c5d80acd9fac2bf4e0f?sid=8084079a-d28b-4b04-9839-cf3544cddc42 + + Expected: The second-step document should reflect the quantities done in the first step, line by line / per destination move, without over- or under-allocation. + Actual: The second-step quantities differ from the first step and/or are misallocated. + Status: Under investigation. + +- Sales (SO line changes not updated on picking) + + Symptom: After confirming a Sales Order, changes made on the SO line are not propagated to the generated picking. + + Source: Video “Moduon - Review Gelo [16.0][ADD] stock_move_not_merge_by_dest_moves #2014 sales”. + https://www.loom.com/share/8dfe51327d6d435f8dbdcef4af9ce77b?sid=424232ef-c392-4903-8d42-0254da24c812 + + Expected: Updates on the SO line (e.g., quantity/discount that affect downstream moves) should be reflected on the generated picking. + Actual: The picking does not refresh after SO line changes. + Status: Under investigation. diff --git a/stock_move_not_merge_by_dest_moves/readme/USAGE.md b/stock_move_not_merge_by_dest_moves/readme/USAGE.md new file mode 100644 index 000000000000..ab1b25eff0de --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/readme/USAGE.md @@ -0,0 +1,9 @@ +To use this module, you need to have activated sales module: + +1. Activate sale module. +1. Go to Warehouse and activate 2 steps shipping process. +1. Create and confirm a sale with 2 lines: One with discount and one without discount with the same product. +1. Confirm the sale. +1. Check the first picking (PICK) moves is not grouped. +1. Set quantity done on each line that differs (below) the reserved quantity on PICK picking and confirm it. +1. Go to the OUT picking, and check how quantities are distributed correctly. diff --git a/stock_move_not_merge_by_dest_moves/static/description/icon.png b/stock_move_not_merge_by_dest_moves/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/stock_move_not_merge_by_dest_moves/static/description/icon.png differ diff --git a/stock_move_not_merge_by_dest_moves/static/description/index.html b/stock_move_not_merge_by_dest_moves/static/description/index.html new file mode 100644 index 000000000000..f56799219b61 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/static/description/index.html @@ -0,0 +1,605 @@ + + + + + +Stock Move - Do not merge by destination moves + + + +
+

Stock Move - Do not merge by destination moves

+ + +

Alpha License: LGPL-3 OCA/stock-logistics-workflow Translate me on Weblate Try me on Runboat

+

This module extends the functionality of stock move merging to allow you +to never merge stock moves if those moves will go to different +destination moves.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Use Cases / Context

+

This module was developed because sometimes we don’t want our stock +movements to be merged if goes to different moves because:

+
    +
  • Sale free products that must be invoiced separatelly (for example, get +2 + 1 free)
  • +
  • Use warehouse with 2 or 3 steps in Outgoing Shipments
  • +
  • Use products with Invoicing Policy: Delivered Quantities
  • +
+

All of these conditions must be met for this module to be really useful.

+
+

Use case example:

+

We have an order with two lines of the same product that need to be +weighed (kg) and the offer is “get 2 + 1 free”.

+

Each piece of Fish measures aproximately 1kg and we assume you know how +many Fishes you need to take.

+

The lines of the sale would be like this:

+
    +
  • 2 pieces of Fish for aproximately 2kg in total.
  • +
  • 1 piece of Fish for aproximately 1kg in total with 100% discount.
  • +
+
+
+

Odoo Core VS. Module: Workflow comparison

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Odoo CoreWith this module
The OUT step will not be +grouped, so we will have 2 +moves.The OUT step will not be +grouped, so we will have 2 +moves.
  
The PICK step will be grouped +into one line, telling you that +3 kgs must be demandedSince we have 2 separate moves +on the OUT step, we don’t want +to merge moves in the PICK step. +PICK step will tell you that +3kgs must be demanded into 2 +separate moves.
  
When you measure the 3 fishes in +the PICK step, we get 1.9kg for +the 2 Fishes and 0.7kg for the +free Fish. 2.6kg in total.When you measure the 3 fishes in +the PICK step, we get 1.9kg for +the 2 Fishes and 0.7kg for the +free Fish. 2.6kg in total.
  
Confirm the PICK step. When you +reserve quantities on OUTGOING +step, 2kg will go to the 2 +Fishes and 0.6kg to the free +Fish.Confirm the PICK step. When you +reserve quantities on OUTGOING +step, 1.9kg will go to the 2 +Fishes and 0.7kg to the free +Fish.
  
Your invoice to the customer +will be 2kg for the 2 pieces and +0.6kg for the free fish.Your invoice to the customer +will be 1.9kg for the 2 pieces +and 0.7kg for the free fish.
  
This is not correct: The 2 pices +of Fish should be invoiced for +1.9kg and the free fish should +be invoiced for 0.7kg at 100% +discount.This is correct
+

If you also don’t want to have to reweigh in the last step if you +exceeded the quantity demanded (Fishes weight 1.14kg each for example), +you might be interested in this module:

+
    +
  • stock_rule_reserve_max_quantity
  • +
+
+
+
+

Usage

+

To use this module, you need to have activated sales module:

+
    +
  1. Activate sale module.
  2. +
  3. Go to Warehouse and activate 2 steps shipping process.
  4. +
  5. Create and confirm a sale with 2 lines: One with discount and one +without discount with the same product.
  6. +
  7. Confirm the sale.
  8. +
  9. Check the first picking (PICK) moves is not grouped.
  10. +
  11. Set quantity done on each line that differs (below) the reserved +quantity on PICK picking and confirm it.
  12. +
  13. Go to the OUT picking, and check how quantities are distributed +correctly.
  14. +
+
+
+

Known issues / Roadmap

+
    +
  • Receipts (second step quantities not correct)

    +

    Symptom: After testing receipts, the quantities shown/propagated in +the second step are not correct (they do not match what was processed +in the first step, or the distribution per move is inconsistent).

    +

    Source: Video “Moduon - Review Gelo [16.0][ADD] +stock_move_not_merge_by_dest_moves #2014”. +https://www.loom.com/share/416b1efb65d04c5d80acd9fac2bf4e0f?sid=8084079a-d28b-4b04-9839-cf3544cddc42

    +

    Expected: The second-step document should reflect the quantities done +in the first step, line by line / per destination move, without over- +or under-allocation. Actual: The second-step quantities differ from +the first step and/or are misallocated. Status: Under investigation.

    +
  • +
  • Sales (SO line changes not updated on picking)

    +

    Symptom: After confirming a Sales Order, changes made on the SO line +are not propagated to the generated picking.

    +

    Source: Video “Moduon - Review Gelo [16.0][ADD] +stock_move_not_merge_by_dest_moves #2014 sales”. +https://www.loom.com/share/8dfe51327d6d435f8dbdcef4af9ce77b?sid=424232ef-c392-4903-8d42-0254da24c812

    +

    Expected: Updates on the SO line (e.g., quantity/discount that affect +downstream moves) should be reflected on the generated picking. +Actual: The picking does not refresh after SO line changes. Status: +Under investigation.

    +
  • +
+
+
+

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

+
    +
  • Moduon
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • Ulzama
  • +
+
+
+

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 maintainers:

+

Shide rafaelbn

+

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_move_not_merge_by_dest_moves/tests/__init__.py b/stock_move_not_merge_by_dest_moves/tests/__init__.py new file mode 100644 index 000000000000..4b33b6f53cee --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_move diff --git a/stock_move_not_merge_by_dest_moves/tests/test_stock_move.py b/stock_move_not_merge_by_dest_moves/tests/test_stock_move.py new file mode 100644 index 000000000000..b529ea9d0be7 --- /dev/null +++ b/stock_move_not_merge_by_dest_moves/tests/test_stock_move.py @@ -0,0 +1,174 @@ +# Copyright 2025 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +# from odoo.tests.common import HttpCase, TransactionCase +from odoo.addons.stock.tests.common import TestStockCommon + + +class TestStockMoveNotMergeByDestMoves(TestStockCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.product_tv = cls.env["product.product"].create( + { + "name": "Product Variable QTYs", + "type": "product", + "categ_id": cls.env.ref("product.product_category_all").id, + } + ) + # Enable pick_ship route + cls.wh = cls.env["stock.warehouse"].search( + [("company_id", "=", cls.env.user.id)], limit=1 + ) + # Get pick ship route and rules + cls.wh.write({"delivery_steps": "pick_ship"}) + # Fixed procurement group + cls.pick_ship_route = cls.wh.route_ids.filtered( + lambda r: "(pick + ship)" in r.name + ) + cls.pick_rule = cls.pick_ship_route.rule_ids.filtered( + lambda rule: "Stock → Output" in rule.name + ) + procurement_group = cls.env["procurement.group"].create({}) + cls.pick_rule.write( + { + "group_propagation_option": "fixed", + "group_id": procurement_group.id, + } + ) + cls.ship_rule = cls.pick_ship_route.rule_ids - cls.pick_rule + # Disable Backorder creation + cls.wh.pick_type_id.write( + {"create_backorder": "never", "reservation_method": "manual"} + ) + cls.wh.out_type_id.write( + {"create_backorder": "never", "reservation_method": "manual"} + ) + + def _create_pick_ship_pickings(self, same_destination_moves: bool = False): + """Create pick and ship pickings with the given stock and move + quantities linking pick and ship moves. + + :param same_destination_moves: If True, the destination moves will be the same + """ + # Locations + stock_location = self.pick_rule.location_src_id + ship_location = self.pick_rule.location_dest_id + customer_location = self.ship_rule.location_dest_id + # Ensure stock + self.env["stock.quant"]._update_available_quantity( + self.product_tv, stock_location, 10.0 + ) + # PICK + pick_picking = self.env["stock.picking"].create( + { + "location_id": stock_location.id, + "location_dest_id": ship_location.id, + "picking_type_id": self.wh.pick_type_id.id, + "state": "draft", + } + ) + pick_move_1 = self.env["stock.move"].create( + { + "name": "pick move 1", + "picking_id": pick_picking.id, + "rule_id": self.pick_rule.id, + "location_id": stock_location.id, + "location_dest_id": ship_location.id, + "product_id": self.product_tv.id, + "product_uom": self.uom_unit.id, + "product_uom_qty": 7.0, + "warehouse_id": self.wh.id, + "group_id": self.pick_rule.group_id.id, + "origin": "test_stock_move_not_merge_by_dest_moves", + "procure_method": "make_to_stock", + } + ) + pick_move_2 = self.env["stock.move"].create( + { + "name": "pick move 2", + "picking_id": pick_picking.id, + "rule_id": self.pick_rule.id, + "location_id": stock_location.id, + "location_dest_id": ship_location.id, + "product_id": self.product_tv.id, + "product_uom": self.uom_unit.id, + "product_uom_qty": 3.0, + "warehouse_id": self.wh.id, + "group_id": self.pick_rule.group_id.id, + "origin": "test_stock_move_not_merge_by_dest_moves", + "procure_method": "make_to_stock", + } + ) + # SHIP + ship_picking = self.env["stock.picking"].create( + { + "location_id": ship_location.id, + "location_dest_id": customer_location.id, + "picking_type_id": self.wh.out_type_id.id, + "state": "confirmed", # We don't want to confirm to avoid merge moves + } + ) + ship_move_1 = self.env["stock.move"].create( + { + "name": "ship move 1", + "picking_id": ship_picking.id, + "rule_id": self.ship_rule.id, + "location_id": ship_location.id, + "location_dest_id": customer_location.id, + "product_id": self.product_tv.id, + "product_uom": self.uom_unit.id, + "product_uom_qty": 10.0, + "warehouse_id": self.wh.id, + "group_id": self.pick_rule.group_id.id, + "origin": "test_stock_move_not_merge_by_dest_moves", + "procure_method": "make_to_stock", + } + ) + # Link moves + if same_destination_moves: + (pick_move_1 | pick_move_2).write({"move_dest_ids": [(4, ship_move_1.id)]}) + ship_move_1.write( + {"move_orig_ids": [(4, pick_move_1.id), (4, pick_move_2.id)]} + ) + else: + ship_move_1.write({"product_uom_qty": 7.0}) + ship_move_2 = self.env["stock.move"].create( + { + "name": "ship move 2", + "picking_id": ship_picking.id, + "rule_id": self.ship_rule.id, + "location_id": ship_location.id, + "location_dest_id": customer_location.id, + "product_id": self.product_tv.id, + "product_uom": self.uom_unit.id, + "product_uom_qty": 3.0, + "warehouse_id": self.wh.id, + "group_id": self.pick_rule.group_id.id, + "origin": "test_stock_move_not_merge_by_dest_moves", + "procure_method": "make_to_stock", + } + ) + pick_move_1.write({"move_dest_ids": [(4, ship_move_1.id)]}) + pick_move_2.write({"move_dest_ids": [(4, ship_move_2.id)]}) + ship_move_1.write({"move_orig_ids": [(4, pick_move_1.id)]}) + ship_move_2.write({"move_orig_ids": [(4, pick_move_2.id)]}) + return pick_picking, ship_picking + + def test_merge_moves(self): + """Test PICK merge moves when destination move is the same""" + pick_picking, ship_picking = self._create_pick_ship_pickings( + same_destination_moves=True, + ) + self.assertEqual(len(pick_picking.move_ids), 2) + pick_picking.action_confirm() + self.assertEqual(len(pick_picking.move_ids), 1) + + def test_not_merge_moves(self): + """Test PICK don't merge moves when destination moves are different""" + pick_picking, ship_picking = self._create_pick_ship_pickings( + same_destination_moves=False, + ) + self.assertEqual(len(pick_picking.move_ids), 2) + pick_picking.action_confirm() + self.assertEqual(len(pick_picking.move_ids), 2)