diff --git a/setup/stock_available_to_promise_release_restrict_lot/odoo/addons/stock_available_to_promise_release_restrict_lot b/setup/stock_available_to_promise_release_restrict_lot/odoo/addons/stock_available_to_promise_release_restrict_lot new file mode 120000 index 0000000000..e9809af3a3 --- /dev/null +++ b/setup/stock_available_to_promise_release_restrict_lot/odoo/addons/stock_available_to_promise_release_restrict_lot @@ -0,0 +1 @@ +../../../../stock_available_to_promise_release_restrict_lot \ No newline at end of file diff --git a/setup/stock_available_to_promise_release_restrict_lot/setup.py b/setup/stock_available_to_promise_release_restrict_lot/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/stock_available_to_promise_release_restrict_lot/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_available_to_promise_release/README.rst b/stock_available_to_promise_release/README.rst index 18da72d2c0..471ddaacd7 100644 --- a/stock_available_to_promise_release/README.rst +++ b/stock_available_to_promise_release/README.rst @@ -139,6 +139,7 @@ Contributors * Dung Tran * Laurent Mignon * Michael Tietz (MT Software) +* Souheil Bejaoui Other credits ~~~~~~~~~~~~~ diff --git a/stock_available_to_promise_release/models/stock_move.py b/stock_available_to_promise_release/models/stock_move.py index d59d90eb59..69b467a1ef 100644 --- a/stock_available_to_promise_release/models/stock_move.py +++ b/stock_available_to_promise_release/models/stock_move.py @@ -401,31 +401,31 @@ def _get_ordered_available_to_promise_by_warehouse(self, warehouse): item["product_id"][0]: item["quantity"] for item in location_quants } for move in self: - product_uom = move.product_id.uom_id - previous_promised_qty = move.previous_promised_qty - - rounding = product_uom.rounding - available_qty = float_round( - quants_available.get(move.product_id.id, 0.0), - precision_rounding=rounding, - ) - - real_promised = available_qty - previous_promised_qty - uom_promised = product_uom._compute_quantity( - real_promised, - move.product_uom, - rounding_method="HALF-UP", - ) - res[move] = { - "ordered_available_to_promise_uom_qty": max( - min(uom_promised, move.product_uom_qty), 0.0 - ), - "ordered_available_to_promise_qty": max( - min(real_promised, move.product_qty), 0.0 - ), - } + available_qty = quants_available.get(move.product_id.id, 0.0) + res[move] = move._get_ordered_available_to_promise_qty(available_qty) return res + def _get_ordered_available_to_promise_qty(self, available_qty): + self.ensure_one() + product_uom = self.product_id.uom_id + rounding = product_uom.rounding + previous_promised_qty = self.previous_promised_qty + available_qty = float_round(available_qty, precision_rounding=rounding) + real_promised = available_qty - previous_promised_qty + uom_promised = product_uom._compute_quantity( + real_promised, + self.product_uom, + rounding_method="HALF-UP", + ) + return { + "ordered_available_to_promise_uom_qty": max( + min(uom_promised, self.product_uom_qty), 0.0 + ), + "ordered_available_to_promise_qty": max( + min(real_promised, self.product_qty), 0.0 + ), + } + def _get_ordered_available_to_promise(self): res = {} moves_by_warehouse = self._group_by_warehouse() diff --git a/stock_available_to_promise_release/readme/CONTRIBUTORS.rst b/stock_available_to_promise_release/readme/CONTRIBUTORS.rst index 137e8a2d97..e52e3b983c 100644 --- a/stock_available_to_promise_release/readme/CONTRIBUTORS.rst +++ b/stock_available_to_promise_release/readme/CONTRIBUTORS.rst @@ -4,3 +4,4 @@ * Dung Tran * Laurent Mignon * Michael Tietz (MT Software) +* Souheil Bejaoui diff --git a/stock_available_to_promise_release/static/description/index.html b/stock_available_to_promise_release/static/description/index.html index 40492af1d4..647c2b8d03 100644 --- a/stock_available_to_promise_release/static/description/index.html +++ b/stock_available_to_promise_release/static/description/index.html @@ -8,11 +8,10 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ +:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z 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. @@ -275,7 +274,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: gray; } /* line numbers */ +pre.code .ln { color: grey; } /* 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 } @@ -301,7 +300,7 @@ span.pre { white-space: pre } -span.problematic, pre.problematic { +span.problematic { color: red } span.section-subtitle { @@ -477,6 +476,7 @@

Contributors

  • Dung Tran <dungtd@trobz.com>
  • Laurent Mignon <laurent.mignon@acsone.eu>
  • Michael Tietz (MT Software) <mtietz@mt-software.de>
  • +
  • Souheil Bejaoui <soueil.bejaoui@acsone.eu>
  • @@ -489,9 +489,7 @@

    Other credits

    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.

    diff --git a/stock_available_to_promise_release_restrict_lot/README.rst b/stock_available_to_promise_release_restrict_lot/README.rst new file mode 100644 index 0000000000..2c8172f820 --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/README.rst @@ -0,0 +1,111 @@ +=============================================== +Stock Available To Promise Release Restrict Lot +=============================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:62462fd44997edc1156593f1be5e631c8944a601041a92e568a58abe559ffbd8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fwms-lightgray.png?logo=github + :target: https://github.com/OCA/wms/tree/16.0/stock_available_to_promise_release_restrict_lot + :alt: OCA/wms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/wms-16-0/wms-16-0-stock_available_to_promise_release_restrict_lot + :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/wms&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module acts as an integration layer between +``stock_available_to_promise_release`` and ``stock_restrict_lot``, +enabling advanced stock allocation based on both available-to-promise +quantities and lot restrictions. + +By combining available-to-promise logic with lot restriction +functionality, this module enhances stock move allocation by: + +- Allowing stock moves to respect both priority and specific lot + allocations. +- Ensuring that available quantities are promised according to move + priority, but only when the lot matches the restriction. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +When both ``stock_available_to_promise_release`` and +``stock_restrict_lot`` modules are installed, the calculation of +quantities available to promise doesn't properly account for lot +restrictions. This results in a priority conflict, where higher-priority +moves that are restricted to a specific lot receive available quantities +from any lot, bypassing their lot restriction. + +The issue manifests as follows: + +- A move with a higher priority, restricted to a specific lot, is + promised the available quantity regardless of lot constraints. +- If the restricted lot is allocated to another move with lower + priority, the lower-priority move can not be promised its designated + lot. + +In effect, lot-restricted moves cannot accurately reserve quantities +based on both priority and specific lot requirements. This can lead to +stock release issues, where low-priority moves fail to secure stock from +their restricted lots due to incorrect available to promise calculations +in higher-priority moves. + +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 +------------ + +- Souheil Bejaoui soueil.bejaoui@acsone.eu + +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/wms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_available_to_promise_release_restrict_lot/__init__.py b/stock_available_to_promise_release_restrict_lot/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_available_to_promise_release_restrict_lot/__manifest__.py b/stock_available_to_promise_release_restrict_lot/__manifest__.py new file mode 100644 index 0000000000..38bfd30924 --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/__manifest__.py @@ -0,0 +1,13 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Stock Available To Promise Release Restrict Lot", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/wms", + "depends": ["stock_available_to_promise_release", "stock_restrict_lot"], + "data": [], + "demo": [], +} diff --git a/stock_available_to_promise_release_restrict_lot/models/__init__.py b/stock_available_to_promise_release_restrict_lot/models/__init__.py new file mode 100644 index 0000000000..6bda2d2428 --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/models/__init__.py @@ -0,0 +1 @@ +from . import stock_move diff --git a/stock_available_to_promise_release_restrict_lot/models/stock_move.py b/stock_available_to_promise_release_restrict_lot/models/stock_move.py new file mode 100644 index 0000000000..a3b04ea8c2 --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/models/stock_move.py @@ -0,0 +1,85 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models +from odoo.osv.expression import AND, OR + + +class StockMove(models.Model): + + _inherit = "stock.move" + + def _previous_promised_qty_sql_lateral_where(self, warehouse): + sql, params = super()._previous_promised_qty_sql_lateral_where(warehouse) + sql += "AND m.restrict_lot_id is null" + return sql, params + + def _get_previous_restricted_lot_moves_domain(self): + return AND( + [ + [ + ("restrict_lot_id", "=", self.restrict_lot_id.id), + ("state", "not in", ["done", "cancel"]), + ("id", "!=", self.id), + ], + OR( + [ + [("priority", ">", self.priority)], + [ + ("priority", "=", self.priority), + ("date_priority", "<", self.date_priority), + ], + [ + ("priority", "=", self.priority), + ("date_priority", "=", self.date_priority), + ("id", "<", self.id), + ], + ] + ), + ] + ) + + def _get_previous_restricted_lot_moves(self): + self.ensure_one() + if not self.restrict_lot_id: + return self.browse() + return self.search(self._get_previous_restricted_lot_moves_domain()) + + def _get_previous_promised_qties(self): + restrict_lot_moves = self.filtered("restrict_lot_id") + no_restrict_lot_moves = self - restrict_lot_moves + res = super(StockMove, no_restrict_lot_moves)._get_previous_promised_qties() + for move in restrict_lot_moves: + previous_moves = self._get_previous_restricted_lot_moves() + res[move.id] = ( + sum(previous_moves.mapped("product_uom_qty")) if previous_moves else 0 + ) + return res + + def _get_ordered_available_to_promise_by_warehouse(self, warehouse): + restrict_lot_moves = self.filtered("restrict_lot_id") + no_restrict_lot_moves = self - restrict_lot_moves + res = super( + StockMove, no_restrict_lot_moves + )._get_ordered_available_to_promise_by_warehouse(warehouse) + if not warehouse: + for move in restrict_lot_moves: + res[move] = { + "ordered_available_to_promise_uom_qty": 0, + "ordered_available_to_promise_qty": 0, + } + return res + location_domain = warehouse.view_location_id._get_available_to_promise_domain() + domain_quant = AND( + [[("lot_id", "in", self.restrict_lot_id.ids)], location_domain] + ) + location_quants = self.env["stock.quant"].read_group( + domain_quant, ["lot_id", "quantity"], ["lot_id"], orderby="id" + ) + quants_available = { + item["lot_id"][0]: item["quantity"] for item in location_quants + } + for move in restrict_lot_moves: + available_qty = quants_available.get(move.restrict_lot_id.id, 0.0) + res[move] = move._get_ordered_available_to_promise_qty(available_qty) + return res diff --git a/stock_available_to_promise_release_restrict_lot/readme/CONTEXT.md b/stock_available_to_promise_release_restrict_lot/readme/CONTEXT.md new file mode 100644 index 0000000000..47b3b6bdbf --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/readme/CONTEXT.md @@ -0,0 +1,14 @@ +When both `stock_available_to_promise_release` and `stock_restrict_lot` modules +are installed, the calculation of quantities available to promise doesn't +properly account for lot restrictions. This results in a priority conflict, +where higher-priority moves that are restricted to a specific lot receive +available quantities from any lot, bypassing their lot restriction. + +The issue manifests as follows: +- A move with a higher priority, restricted to a specific lot, is promised the available quantity regardless of lot constraints. +- If the restricted lot is allocated to another move with lower priority, the lower-priority move can not be promised its designated lot. + +In effect, lot-restricted moves cannot accurately reserve quantities based on +both priority and specific lot requirements. +This can lead to stock release issues, where low-priority moves fail to secure +stock from their restricted lots due to incorrect available to promise calculations in higher-priority moves. \ No newline at end of file diff --git a/stock_available_to_promise_release_restrict_lot/readme/CONTRIBUTORS.md b/stock_available_to_promise_release_restrict_lot/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..2c84ca6948 --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +* Souheil Bejaoui diff --git a/stock_available_to_promise_release_restrict_lot/readme/DESCRIPTION.md b/stock_available_to_promise_release_restrict_lot/readme/DESCRIPTION.md new file mode 100644 index 0000000000..d0785fbf93 --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +This module acts as an integration layer between `stock_available_to_promise_release` +and `stock_restrict_lot`, enabling advanced stock allocation based on both +available-to-promise quantities and lot restrictions. + +By combining available-to-promise logic with lot restriction functionality, +this module enhances stock move allocation by: +- Allowing stock moves to respect both priority and specific lot allocations. +- Ensuring that available quantities are promised according to move priority, but only when the lot matches the restriction. diff --git a/stock_available_to_promise_release_restrict_lot/static/description/icon.png b/stock_available_to_promise_release_restrict_lot/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/stock_available_to_promise_release_restrict_lot/static/description/icon.png differ diff --git a/stock_available_to_promise_release_restrict_lot/static/description/index.html b/stock_available_to_promise_release_restrict_lot/static/description/index.html new file mode 100644 index 0000000000..e9fce10c1d --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/static/description/index.html @@ -0,0 +1,454 @@ + + + + + +Stock Available To Promise Release Restrict Lot + + + +
    +

    Stock Available To Promise Release Restrict Lot

    + + +

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

    +

    This module acts as an integration layer between +stock_available_to_promise_release and stock_restrict_lot, +enabling advanced stock allocation based on both available-to-promise +quantities and lot restrictions.

    +

    By combining available-to-promise logic with lot restriction +functionality, this module enhances stock move allocation by:

    +
      +
    • Allowing stock moves to respect both priority and specific lot +allocations.
    • +
    • Ensuring that available quantities are promised according to move +priority, but only when the lot matches the restriction.
    • +
    +

    Table of contents

    + +
    +

    Use Cases / Context

    +

    When both stock_available_to_promise_release and +stock_restrict_lot modules are installed, the calculation of +quantities available to promise doesn’t properly account for lot +restrictions. This results in a priority conflict, where higher-priority +moves that are restricted to a specific lot receive available quantities +from any lot, bypassing their lot restriction.

    +

    The issue manifests as follows:

    +
      +
    • A move with a higher priority, restricted to a specific lot, is +promised the available quantity regardless of lot constraints.
    • +
    • If the restricted lot is allocated to another move with lower +priority, the lower-priority move can not be promised its designated +lot.
    • +
    +

    In effect, lot-restricted moves cannot accurately reserve quantities +based on both priority and specific lot requirements. This can lead to +stock release issues, where low-priority moves fail to secure stock from +their restricted lots due to incorrect available to promise calculations +in higher-priority moves.

    +
    +
    +

    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

    + +
    +
    +

    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/wms project on GitHub.

    +

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

    +
    +
    +
    + + diff --git a/stock_available_to_promise_release_restrict_lot/tests/__init__.py b/stock_available_to_promise_release_restrict_lot/tests/__init__.py new file mode 100644 index 0000000000..5b3bffad4c --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_available_to_promise_release_restrict_lot diff --git a/stock_available_to_promise_release_restrict_lot/tests/test_stock_available_to_promise_release_restrict_lot.py b/stock_available_to_promise_release_restrict_lot/tests/test_stock_available_to_promise_release_restrict_lot.py new file mode 100644 index 0000000000..867f2708f1 --- /dev/null +++ b/stock_available_to_promise_release_restrict_lot/tests/test_stock_available_to_promise_release_restrict_lot.py @@ -0,0 +1,203 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.tests.common import TransactionCase + + +class TestStockAvailableToPromiseReleaseRestrictLot(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.location_wh_stock = cls.env.ref("stock.stock_location_stock") + cls.location_customer = cls.env.ref("stock.stock_location_customers") + cls.picking_type = cls.env.ref("stock.picking_type_out") + cls.product = cls.env["product.product"].create( + {"name": "Test Product", "type": "product", "tracking": "lot"} + ) + cls.lot_1 = cls.env["stock.lot"].create( + {"name": "Lot 1", "product_id": cls.product.id} + ) + cls.lot_2 = cls.env["stock.lot"].create( + {"name": "Lot 2", "product_id": cls.product.id} + ) + + # Create stock pickings and moves with different priorities + cls.picking_1 = cls.env["stock.picking"].create( + { + "priority": "1", + "picking_type_id": cls.picking_type.id, + "location_id": cls.location_wh_stock.id, + "location_dest_id": cls.location_customer.id, + "move_ids": [ + Command.create( + { + "name": "move 1", + "warehouse_id": cls.warehouse.id, + "product_id": cls.product.id, + "product_uom_qty": 10, + "product_uom": cls.product.uom_id.id, + "location_id": cls.location_wh_stock.id, + "location_dest_id": cls.location_customer.id, + } + ) + ], + } + ) + cls.move_priority_high = cls.picking_1.move_ids + + cls.picking_2 = cls.env["stock.picking"].create( + { + "priority": "0", + "picking_type_id": cls.picking_type.id, + "location_id": cls.location_wh_stock.id, + "location_dest_id": cls.location_customer.id, + "move_ids": [ + Command.create( + { + "name": "move 2", + "warehouse_id": cls.warehouse.id, + "product_id": cls.product.id, + "product_uom_qty": 10, + "product_uom": cls.product.uom_id.id, + "location_id": cls.location_wh_stock.id, + "location_dest_id": cls.location_customer.id, + } + ) + ], + } + ) + cls.move_priority_low = cls.picking_2.move_ids + + # Confirm and assign the pickings + cls.picking_1.action_confirm() + cls.picking_1.action_assign() + cls.picking_2.action_confirm() + cls.picking_2.action_assign() + + @classmethod + def _update_qty_in_location(cls, location, product, quantity, lot=None): + cls.env["stock.quant"]._update_available_quantity( + product, location, quantity, lot_id=lot + ) + cls.env["product.product"].invalidate_model( + fnames=[ + "qty_available", + "virtual_available", + "incoming_qty", + "outgoing_qty", + ] + ) + + def test_0(self): + """ + Test standard allocation behavior without lot restrictions. + Verifies that moves with higher priority are allocated available quantities + first. + """ + self._update_qty_in_location( + self.location_wh_stock, self.product, 8, self.lot_1 + ) + self.assertEqual(self.move_priority_high.previous_promised_qty, 0) + self.assertEqual(self.move_priority_high.ordered_available_to_promise_qty, 8) + self.assertEqual(self.move_priority_low.previous_promised_qty, 10) + self.assertEqual(self.move_priority_low.ordered_available_to_promise_qty, 0) + + def test_1(self): + """ + Test a high-priority move restricted to a specific lot with no available stock + in that lot. + Verifies that a restricted lot move does not interfere with another move's + allocation. + """ + self.move_priority_high.restrict_lot_id = self.lot_2 + self._update_qty_in_location( + self.location_wh_stock, self.product, 8, self.lot_1 + ) + self.assertEqual(self.move_priority_high.previous_promised_qty, 0) + self.assertEqual(self.move_priority_high.ordered_available_to_promise_qty, 0) + self.assertEqual(self.move_priority_low.previous_promised_qty, 0) + self.assertEqual(self.move_priority_low.ordered_available_to_promise_qty, 8) + + def test_2(self): + """ + Test a low-priority move restricted to a specific lot without interfering with + high-priority allocation. + Verifies that low-priority restrictions don’t reduce available quantities + for high-priority moves. + """ + self.move_priority_low.restrict_lot_id = self.lot_2 + self._update_qty_in_location( + self.location_wh_stock, self.product, 8, self.lot_1 + ) + self.assertEqual(self.move_priority_high.previous_promised_qty, 0) + self.assertEqual(self.move_priority_high.ordered_available_to_promise_qty, 8) + self.assertEqual(self.move_priority_low.previous_promised_qty, 0) + self.assertEqual(self.move_priority_low.ordered_available_to_promise_qty, 0) + + def test_3(self): + """ + Test both moves with lot restrictions and available quantities in each lot. + Verifies that each move is allocated quantities based on its lot restriction. + """ + self.move_priority_high.restrict_lot_id = self.lot_1 + self.move_priority_low.restrict_lot_id = self.lot_2 + self._update_qty_in_location( + self.location_wh_stock, self.product, 8, self.lot_1 + ) + self._update_qty_in_location( + self.location_wh_stock, self.product, 5, self.lot_2 + ) + self.assertEqual(self.move_priority_high.previous_promised_qty, 0) + self.assertEqual(self.move_priority_high.ordered_available_to_promise_qty, 8) + self.assertEqual(self.move_priority_low.previous_promised_qty, 0) + self.assertEqual(self.move_priority_low.ordered_available_to_promise_qty, 5) + + def test_4(self): + """ + Test both moves with lot restrictions to the same lot. + Verifies that each move is allocated quantities based on its lot restriction + respecting the priority. + """ + self.move_priority_high.restrict_lot_id = self.lot_1 + self.move_priority_low.restrict_lot_id = self.lot_1 + self._update_qty_in_location( + self.location_wh_stock, self.product, 15, self.lot_1 + ) + self.assertEqual(self.move_priority_high.previous_promised_qty, 0) + self.assertEqual(self.move_priority_high.ordered_available_to_promise_qty, 10) + self.assertEqual(self.move_priority_low.previous_promised_qty, 10) + self.assertEqual(self.move_priority_low.ordered_available_to_promise_qty, 5) + + def test_5(self): + """ + Test both moves with lot restrictions to the same lot with partial availability. + Verifies that each move is allocated quantities based on its lot restriction + respecting the priority. + """ + self.move_priority_high.restrict_lot_id = self.lot_1 + self.move_priority_low.restrict_lot_id = self.lot_1 + self._update_qty_in_location( + self.location_wh_stock, self.product, 8, self.lot_1 + ) + self.assertEqual(self.move_priority_high.previous_promised_qty, 0) + self.assertEqual(self.move_priority_high.ordered_available_to_promise_qty, 8) + self.assertEqual(self.move_priority_low.previous_promised_qty, 10) + self.assertEqual(self.move_priority_low.ordered_available_to_promise_qty, 0) + + def test_6(self): + """ + Test both moves with lot restrictions to the same lot with no availability. + Verifies that each move is allocated quantities based on its lot restriction + respecting the priority. + """ + self.move_priority_high.restrict_lot_id = self.lot_1 + self.move_priority_low.restrict_lot_id = self.lot_1 + self._update_qty_in_location( + self.location_wh_stock, self.product, 8, self.lot_2 + ) + self.assertEqual(self.move_priority_high.previous_promised_qty, 0) + self.assertEqual(self.move_priority_high.ordered_available_to_promise_qty, 0) + self.assertEqual(self.move_priority_low.previous_promised_qty, 10) + self.assertEqual(self.move_priority_low.ordered_available_to_promise_qty, 0)