From b9041768c3680a44ca0ca5553980ebaa51ab041c Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Wed, 24 Jun 2026 08:21:31 +0200 Subject: [PATCH 1/5] [IMP] stock_full_location_reservation: Don't merge if products are different --- stock_full_location_reservation/models/stock_move.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stock_full_location_reservation/models/stock_move.py b/stock_full_location_reservation/models/stock_move.py index cdc0fae7f7d..31c596b67a2 100644 --- a/stock_full_location_reservation/models/stock_move.py +++ b/stock_full_location_reservation/models/stock_move.py @@ -72,7 +72,10 @@ def _full_location_reservation_create_move( product, qty, location, package ) ) - if self.picking_type_id.merge_move_for_full_location_reservation: + if ( + self.product_id == product + and self.picking_type_id.merge_move_for_full_location_reservation + ): # To be able to be merged, the new move should use the same source location as # the original one. new_move.location_id = self.location_id From 8d5db38f858d7a79c7005d609ee791be7c8df9ce Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Wed, 24 Jun 2026 08:22:08 +0200 Subject: [PATCH 2/5] [IMP] stock_full_location_reservation: Introduce a strict mode As in some flows we need to limit the full reservation to the move line characteristics (lot, package, owner), add a strict mode that will use the Odoo core mechanism to gather the needed quant(ities) --- .../models/stock_move.py | 6 +- .../models/stock_move_line.py | 55 ++++++++++----- .../tests/common.py | 6 ++ .../tests/test_full_location_reservation.py | 67 +++++++++++++++++++ 4 files changed, 117 insertions(+), 17 deletions(-) diff --git a/stock_full_location_reservation/models/stock_move.py b/stock_full_location_reservation/models/stock_move.py index 31c596b67a2..6fd10486411 100644 --- a/stock_full_location_reservation/models/stock_move.py +++ b/stock_full_location_reservation/models/stock_move.py @@ -89,5 +89,7 @@ def _full_location_reservation_create_move( ) return new_move - def _full_location_reservation(self, package_only=None): - return self.move_line_ids._full_location_reservation(package_only) + def _full_location_reservation(self, strict=False, package_only=None): + return self.move_line_ids._full_location_reservation( + strict=strict, package_only=package_only + ) diff --git a/stock_full_location_reservation/models/stock_move_line.py b/stock_full_location_reservation/models/stock_move_line.py index aee2844f9bc..bbbff8ed582 100644 --- a/stock_full_location_reservation/models/stock_move_line.py +++ b/stock_full_location_reservation/models/stock_move_line.py @@ -42,23 +42,48 @@ def _get_full_location_reservable_qties(self, package_only=None): ] += qty_available return res - def _full_location_reservation(self, package_only=None): - reservable_qties = self._get_full_location_reservable_qties(package_only) + def _full_location_reservation(self, strict=False, package_only=None): moves_to_assign_ids = [] - for line in self.exists(): # Move line should have been deleted - # Copy location and package as move line could be deleted if merge occurs - location = line.location_id - package = line.package_id - qties = reservable_qties.get(location, {}).get(package, {}) - if not qties: - continue - for product, qty in qties.items(): - moves_to_assign_ids.append( - line.move_id._full_location_reservation_create_move( - product, qty, location, package - ).id + if not strict: + reservable_qties = self._get_full_location_reservable_qties( + package_only=package_only + ) + for line in self.exists(): + location = line.location_id + package = line.package_id + qties = reservable_qties.get(location, {}).get(package, {}) + if not qties: + continue + for product, qty in qties.items(): + moves_to_assign_ids.append( + line.move_id._full_location_reservation_create_move( + product, qty, location, package + ).id + ) + reservable_qties[location].pop(package) + + else: + # Use Odoo core mechanism + Quant = self.env["stock.quant"] + + for line in self.exists(): # Move line should have been deleted + quants = Quant._gather( + line.product_id, + line.location_id, + lot_id=line.lot_id, + package_id=line.package_id, + owner_id=line.owner_id, + strict=strict, ) - reservable_qties[location].pop(package) + if not quants: + continue + + total_quantity = 0.0 + for quant in quants: + total_quantity += quant.available_quantity + # We let the core mechanism occur that will reserve + # the needed quants + line.reserved_uom_qty += total_quantity moves_to_assign = self.env["stock.move"].browse(moves_to_assign_ids) if moves_to_assign: moves_to_assign._action_confirm() diff --git a/stock_full_location_reservation/tests/common.py b/stock_full_location_reservation/tests/common.py index ede76445b32..1e190d1f4d1 100644 --- a/stock_full_location_reservation/tests/common.py +++ b/stock_full_location_reservation/tests/common.py @@ -16,6 +16,12 @@ def setUpClass(cls): cls.location_rack_child = cls.location.create( {"name": "Rack child", "location_id": cls.location_rack.id} ) + cls.location_rack_child_2 = cls.location.create( + {"name": "Rack child 2", "location_id": cls.location_rack.id} + ) + cls.location_rack_child_3 = cls.location.create( + {"name": "Rack child 3", "location_id": cls.location_rack.id} + ) cls.customer_location = cls.env.ref("stock.stock_location_customers") def _create_quant(self, product, location, qty, package=None): diff --git a/stock_full_location_reservation/tests/test_full_location_reservation.py b/stock_full_location_reservation/tests/test_full_location_reservation.py index 8bb0267e375..a231740b4c4 100644 --- a/stock_full_location_reservation/tests/test_full_location_reservation.py +++ b/stock_full_location_reservation/tests/test_full_location_reservation.py @@ -51,6 +51,72 @@ def test_full_location_reservation(self): self._check_move_line_len(picking, 1) self._check_move_line_len(picking, 0, self._filter_func) + def test_multi_lines_per_move(self): + """ + We create : + + - Quantity of 10 on Rack of Product A + - Quantity of 10 on Rack 2 of Product A + - Quantity of 10 on Rack 3 of Product A + + We create a picking of 30 from parent Rack, all quantities should + be reserved. + + Then, we update the Rack to 60 and we launch the full reservation + on move 1. + + The moves lines should remains, the reserved total quantity should + be 80 when the original demand should remain. + + """ + self.picking_type.merge_move_for_full_location_reservation = True + self._create_quants( + [ + (self.productA, self.location_rack_child, 10.0), + (self.productA, self.location_rack_child_2, 10.0), + (self.productA, self.location_rack_child_3, 10.0), + (self.productB, self.location_rack_child, 15.0), + ] + ) + picking = self._create_picking( + self.location_rack, + self.customer_location, + self.picking_type, + [[self.productA, 30]], + ) + picking.action_confirm() + picking.action_assign() + self.assertEqual(1, len(picking.move_ids)) + move_line_ids = picking.move_line_ids + self.assertEqual(3, len(picking.move_line_ids)) + + self._create_quants( + [ + (self.productA, self.location_rack_child, 50.0), + ] + ) + + self.assertEqual( + 60.0, + self.productA.with_context( + location=self.location_rack_child.id + ).qty_available, + ) + + picking.move_line_ids[0]._full_location_reservation(strict=True) + + self.assertEqual(3, len(picking.move_line_ids)) + + self.assertEqual( + move_line_ids, + picking.move_line_ids, + ) + self.assertEqual(picking.move_line_ids[0].reserved_uom_qty, 60.0) + + # The original demand stays at 30.0 + self.assertEqual(picking.move_ids.product_uom_qty, 30.0) + self.assertEqual(picking.move_ids.reserved_availability, 80.0) + def test_multiple_pickings(self): picking = self._create_picking( self.location_rack, @@ -141,5 +207,6 @@ def test_full_location_reservation_merge(self): picking.do_full_location_reservation() self._check_move_line_len(picking, 1) + # The original demand remains the same self.assertEqual(10.0, picking.move_ids.product_uom_qty) self.assertEqual(10.0, picking.move_ids.reserved_availability) From 11b7e5884d78613934d858030a29344f096419dd Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Wed, 24 Jun 2026 08:46:04 +0200 Subject: [PATCH 3/5] [IMP] stock_full_location_reservation: Add reservation_mode Replace the 'strict' and 'package_only' parameters with a new reservation_mode parameter that can be set to "strict", "package" . The default behavior is the same as before but it ensures that the reservation mode is unique and can be extended in the future with new modes. --- .../models/stock_move.py | 15 +++++++++++-- .../models/stock_move_line.py | 22 ++++++++++++++----- .../tests/test_full_location_reservation.py | 4 ++-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/stock_full_location_reservation/models/stock_move.py b/stock_full_location_reservation/models/stock_move.py index 6fd10486411..4d002c2d32b 100644 --- a/stock_full_location_reservation/models/stock_move.py +++ b/stock_full_location_reservation/models/stock_move.py @@ -1,5 +1,7 @@ # Copyright 2023 Michael Tietz (MT Software) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import warnings + from odoo import fields, models @@ -89,7 +91,16 @@ def _full_location_reservation_create_move( ) return new_move - def _full_location_reservation(self, strict=False, package_only=None): + def _full_location_reservation(self, reservation_mode=None, **kwargs): + if "package_only" in kwargs: + warnings.warn( + "The 'package_only' parameter is deprecated. " + "Use reservation_mode='package' instead.", + DeprecationWarning, + stacklevel=2, + ) + if kwargs.pop("package_only"): + reservation_mode = "package" return self.move_line_ids._full_location_reservation( - strict=strict, package_only=package_only + reservation_mode=reservation_mode ) diff --git a/stock_full_location_reservation/models/stock_move_line.py b/stock_full_location_reservation/models/stock_move_line.py index bbbff8ed582..5ac3e656800 100644 --- a/stock_full_location_reservation/models/stock_move_line.py +++ b/stock_full_location_reservation/models/stock_move_line.py @@ -1,6 +1,8 @@ # Copyright 2023 Michael Tietz (MT Software) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import warnings from collections import defaultdict +from typing import Literal from odoo import models from odoo.osv import expression @@ -42,7 +44,20 @@ def _get_full_location_reservable_qties(self, package_only=None): ] += qty_available return res - def _full_location_reservation(self, strict=False, package_only=None): + def _full_location_reservation( + self, reservation_mode: Literal["strict", "package"] | None = None, **kwargs + ): + if "package_only" in kwargs: + warnings.warn( + "The 'package_only' parameter is deprecated. " + "Use reservation_mode='package' instead.", + DeprecationWarning, + stacklevel=2, + ) + if kwargs.pop("package_only"): + reservation_mode = "package" + strict = reservation_mode == "strict" + package_only = reservation_mode == "package" moves_to_assign_ids = [] if not strict: reservable_qties = self._get_full_location_reservable_qties( @@ -61,11 +76,9 @@ def _full_location_reservation(self, strict=False, package_only=None): ).id ) reservable_qties[location].pop(package) - else: # Use Odoo core mechanism Quant = self.env["stock.quant"] - for line in self.exists(): # Move line should have been deleted quants = Quant._gather( line.product_id, @@ -73,11 +86,10 @@ def _full_location_reservation(self, strict=False, package_only=None): lot_id=line.lot_id, package_id=line.package_id, owner_id=line.owner_id, - strict=strict, + strict=True, ) if not quants: continue - total_quantity = 0.0 for quant in quants: total_quantity += quant.available_quantity diff --git a/stock_full_location_reservation/tests/test_full_location_reservation.py b/stock_full_location_reservation/tests/test_full_location_reservation.py index a231740b4c4..c5fd5c038ae 100644 --- a/stock_full_location_reservation/tests/test_full_location_reservation.py +++ b/stock_full_location_reservation/tests/test_full_location_reservation.py @@ -103,7 +103,7 @@ def test_multi_lines_per_move(self): ).qty_available, ) - picking.move_line_ids[0]._full_location_reservation(strict=True) + picking.move_line_ids[0]._full_location_reservation(reservation_mode="strict") self.assertEqual(3, len(picking.move_line_ids)) @@ -170,7 +170,7 @@ def test_package_only(self): self.assertEqual(picking.move_line_ids.package_id, package) self._check_move_line_len(picking, 2) - picking.move_ids._full_location_reservation(package_only=True) + picking.move_ids._full_location_reservation(reservation_mode="package") self._check_move_line_len(picking, 3) self.assertEqual(picking.move_line_ids.package_id, package) self.assertEqual(sum(picking.move_line_ids.mapped("reserved_qty")), 11) From 9556d2ac2a666cc974a9a9f70faf6cdc6858116e Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Wed, 24 Jun 2026 09:37:24 +0200 Subject: [PATCH 4/5] [IMP] stock_full_location_reservation: Add new reservation_mode "product" Added a new reservation_mode "product" to the stock_full_location_reservation module. This mode allows for reserving all the same product at location, regardless of the package or lot. --- .../models/stock_move.py | 7 +- .../models/stock_move_line.py | 149 ++++++++++++------ .../tests/test_full_location_reservation.py | 71 +++++++++ 3 files changed, 180 insertions(+), 47 deletions(-) diff --git a/stock_full_location_reservation/models/stock_move.py b/stock_full_location_reservation/models/stock_move.py index 4d002c2d32b..ea5b373688f 100644 --- a/stock_full_location_reservation/models/stock_move.py +++ b/stock_full_location_reservation/models/stock_move.py @@ -1,6 +1,7 @@ # Copyright 2023 Michael Tietz (MT Software) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import warnings +from typing import Literal from odoo import fields, models @@ -91,7 +92,11 @@ def _full_location_reservation_create_move( ) return new_move - def _full_location_reservation(self, reservation_mode=None, **kwargs): + def _full_location_reservation( + self, + reservation_mode: Literal["strict", "package", "product"] | None = None, + **kwargs + ): if "package_only" in kwargs: warnings.warn( "The 'package_only' parameter is deprecated. " diff --git a/stock_full_location_reservation/models/stock_move_line.py b/stock_full_location_reservation/models/stock_move_line.py index 5ac3e656800..33b92574d26 100644 --- a/stock_full_location_reservation/models/stock_move_line.py +++ b/stock_full_location_reservation/models/stock_move_line.py @@ -12,7 +12,9 @@ class StockMoveLine(models.Model): _inherit = "stock.move.line" - def _prepare_full_location_reservation_quants_domain(self, package_only=None): + def _prepare_full_location_reservation_quants_domain( + self, package_only=None, product_only=False + ): domains = [] for line in self: domain = [("location_id", "=", line.location_id.id)] @@ -21,15 +23,25 @@ def _prepare_full_location_reservation_quants_domain(self, package_only=None): domain += [("package_id", "=", line.package_id.id)] else: continue + if product_only: + domain += [("product_id", "=", line.product_id.id)] domains.append(domain) return expression.OR(domains) - def _get_full_location_reservation_quants(self, package_only=None): - domain = self._prepare_full_location_reservation_quants_domain(package_only) + def _get_full_location_reservation_quants( + self, package_only=None, product_only=False + ): + domain = self._prepare_full_location_reservation_quants_domain( + package_only=package_only, product_only=product_only + ) return self.env["stock.quant"].search(domain) - def _get_full_location_reservable_qties(self, package_only=None): - quants = self._get_full_location_reservation_quants(package_only) + def _get_full_location_reservable_qties( + self, package_only=None, product_only=False + ): + quants = self._get_full_location_reservation_quants( + package_only=package_only, product_only=product_only + ) res = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: 0))) for quant in quants: qty_available = quant.available_quantity @@ -44,8 +56,84 @@ def _get_full_location_reservable_qties(self, package_only=None): ] += qty_available return res + def _full_location_reservation_strict(self): + """Reserve using Odoo's _gather with strict=True (exact lot/package/owner match). + + Increments reserved_uom_qty on existing move lines - no new moves created. + """ + Quant = self.env["stock.quant"] + for line in self.exists(): # Move line should have been deleted + quants = Quant._gather( + line.product_id, + line.location_id, + lot_id=line.lot_id, + package_id=line.package_id, + owner_id=line.owner_id, + strict=True, + ) + if not quants: + continue + # We let the core mechanism occur that will reserve the needed quants + line.reserved_uom_qty += sum(q.available_quantity for q in quants) + + def _full_location_reservation_product(self): + """Reserve all available qty of each line's product across every package + at the location - creates one new move per (package, product) combination. + """ + move_ids = [] + reservable_qties = self._get_full_location_reservable_qties(product_only=True) + for line in self.exists(): + location_qties = reservable_qties.get(line.location_id, {}) + for package in list(location_qties.keys()): + package_qties = location_qties[package] + qty = package_qties.pop(line.product_id, 0) + if not package_qties: + location_qties.pop(package) + if qty: + move_ids.append( + line.move_id._full_location_reservation_create_move( + line.product_id, qty, line.location_id, package + ).id + ) + return move_ids + + def _full_location_reservation_by_location(self, package_only=False): + """Shared implementation: creates one new move per (location, package, product) + combination found in reservable quantities. + """ + move_ids = [] + reservable_qties = self._get_full_location_reservable_qties( + package_only=package_only + ) + for line in self.exists(): + location = line.location_id + package = line.package_id + qties = reservable_qties.get(location, {}).get(package, {}) + if not qties: + continue + for product, qty in qties.items(): + move_ids.append( + line.move_id._full_location_reservation_create_move( + product, qty, location, package + ).id + ) + reservable_qties[location].pop(package) + return move_ids + + def _full_location_reservation_package(self): + """Reserve all products at each line's (location, package), + skipping lines that have no package. + """ + return self._full_location_reservation_by_location(package_only=True) + + def _full_location_reservation_default(self): + """Reserve all products at each line's (location, package).""" + return self._full_location_reservation_by_location() + def _full_location_reservation( - self, reservation_mode: Literal["strict", "package"] | None = None, **kwargs + self, + reservation_mode: Literal["strict", "package", "product"] | None = None, + **kwargs, ): if "package_only" in kwargs: warnings.warn( @@ -56,47 +144,16 @@ def _full_location_reservation( ) if kwargs.pop("package_only"): reservation_mode = "package" - strict = reservation_mode == "strict" - package_only = reservation_mode == "package" - moves_to_assign_ids = [] - if not strict: - reservable_qties = self._get_full_location_reservable_qties( - package_only=package_only - ) - for line in self.exists(): - location = line.location_id - package = line.package_id - qties = reservable_qties.get(location, {}).get(package, {}) - if not qties: - continue - for product, qty in qties.items(): - moves_to_assign_ids.append( - line.move_id._full_location_reservation_create_move( - product, qty, location, package - ).id - ) - reservable_qties[location].pop(package) + if reservation_mode == "strict": + self._full_location_reservation_strict() + return self.env["stock.move"] + elif reservation_mode == "product": + move_ids = self._full_location_reservation_product() + elif reservation_mode == "package": + move_ids = self._full_location_reservation_package() else: - # Use Odoo core mechanism - Quant = self.env["stock.quant"] - for line in self.exists(): # Move line should have been deleted - quants = Quant._gather( - line.product_id, - line.location_id, - lot_id=line.lot_id, - package_id=line.package_id, - owner_id=line.owner_id, - strict=True, - ) - if not quants: - continue - total_quantity = 0.0 - for quant in quants: - total_quantity += quant.available_quantity - # We let the core mechanism occur that will reserve - # the needed quants - line.reserved_uom_qty += total_quantity - moves_to_assign = self.env["stock.move"].browse(moves_to_assign_ids) + move_ids = self._full_location_reservation_default() + moves_to_assign = self.env["stock.move"].browse(move_ids) if moves_to_assign: moves_to_assign._action_confirm() moves_to_assign._action_assign() diff --git a/stock_full_location_reservation/tests/test_full_location_reservation.py b/stock_full_location_reservation/tests/test_full_location_reservation.py index c5fd5c038ae..21699ef5f84 100644 --- a/stock_full_location_reservation/tests/test_full_location_reservation.py +++ b/stock_full_location_reservation/tests/test_full_location_reservation.py @@ -175,6 +175,77 @@ def test_package_only(self): self.assertEqual(picking.move_line_ids.package_id, package) self.assertEqual(sum(picking.move_line_ids.mapped("reserved_qty")), 11) + def test_product_mode(self): + """ + Location has productA and productB. + After product mode on a productA move: only productA gets a new + full-reservation move — productB is left untouched. + """ + self._create_quants( + [ + (self.productA, self.location_rack_child, 10.0), + (self.productB, self.location_rack_child, 10.0), + ] + ) + picking = self._create_picking( + self.location_rack, + self.customer_location, + self.picking_type, + [[self.productA, 1]], + ) + picking.action_confirm() + picking.action_assign() + + self._check_move_line_len(picking, 1) + picking.move_ids._full_location_reservation(reservation_mode="product") + # Original move + 1 new full-reservation move for remaining productA + self._check_move_line_len(picking, 2) + self._check_move_line_len(picking, 1, self._filter_func) + full_reservation_moves = picking.move_ids.filtered(self._filter_func) + self.assertEqual(full_reservation_moves.product_id, self.productA) + # Total reserved = all 10 units of productA; productB untouched + self.assertEqual( + sum(picking.move_line_ids.mapped("reserved_uom_qty")), + 10.0, + ) + + def test_product_mode_multiple_packages(self): + """ + Location has productA in two package combinations (packaged and + unpackaged) plus productB. Move for productA (no package). + After product mode: new moves are created for each package combination + of productA — productB remains untouched. + """ + package = self.env["stock.quant.package"].create({"name": "test package"}) + self._create_quants( + [ + (self.productA, self.location_rack_child, 5.0), + (self.productA, self.location_rack_child, 7.0, package), + (self.productB, self.location_rack_child, 10.0), + ] + ) + picking = self._create_picking( + self.location_rack, + self.customer_location, + self.picking_type, + [[self.productA, 1]], + ) + picking.action_confirm() + picking.action_assign() + + self._check_move_line_len(picking, 1) + picking.move_ids._full_location_reservation(reservation_mode="product") + # Original move + 2 new full-reservation moves (one per package combo) + self._check_move_line_len(picking, 3) + self._check_move_line_len(picking, 2, self._filter_func) + full_reservation_moves = picking.move_ids.filtered(self._filter_func) + self.assertEqual(full_reservation_moves.product_id, self.productA) + # Total reserved: 1 (original) + 4 (remaining no-pkg) + 7 (packaged) = 12 + self.assertEqual( + sum(picking.move_line_ids.mapped("reserved_uom_qty")), + 12.0, + ) + def test_full_location_reservation_merge(self): """ Activate the merge for new quantity move. From 4d2d5b977134faf643d3ac6d4779bc73c7a75367 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Wed, 24 Jun 2026 09:49:24 +0200 Subject: [PATCH 5/5] [IMP] stock_full_location_reservation: ACSONE as author --- stock_full_location_reservation/__manifest__.py | 2 +- stock_full_location_reservation/models/stock_move_line.py | 1 + stock_full_location_reservation/readme/CONTRIBUTORS.rst | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/stock_full_location_reservation/__manifest__.py b/stock_full_location_reservation/__manifest__.py index 18a569e4740..aa5de32d3aa 100644 --- a/stock_full_location_reservation/__manifest__.py +++ b/stock_full_location_reservation/__manifest__.py @@ -2,7 +2,7 @@ { "name": "Stock full location reservation", "summary": "Extend reservation to full content of location", - "author": "MT Software, BCIM, Odoo Community Association (OCA)", + "author": "MT Software, BCIM, ACSONE SA/NV, Odoo Community Association (OCA)", "website": "https://github.com/OCA/wms", "category": "Warehouse Management", "version": "16.0.1.0.0", diff --git a/stock_full_location_reservation/models/stock_move_line.py b/stock_full_location_reservation/models/stock_move_line.py index 33b92574d26..54b0da5dd4c 100644 --- a/stock_full_location_reservation/models/stock_move_line.py +++ b/stock_full_location_reservation/models/stock_move_line.py @@ -1,3 +1,4 @@ +# Copyright 2026 ACSONE SA/NV # Copyright 2023 Michael Tietz (MT Software) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import warnings diff --git a/stock_full_location_reservation/readme/CONTRIBUTORS.rst b/stock_full_location_reservation/readme/CONTRIBUTORS.rst index 866de0e85cb..679723b3255 100644 --- a/stock_full_location_reservation/readme/CONTRIBUTORS.rst +++ b/stock_full_location_reservation/readme/CONTRIBUTORS.rst @@ -1,3 +1,4 @@ * Michael Tietz (MT Software) * Jacques-Etienne Baudoux (BCIM) * Denis Roussel +* Laurent Mignon