[FIX] stock_location_orderpoint

Check orderpoints also for non relocated moves
Fix quantity to replenish in case of partial source relocation
This commit is contained in:
Jacques-Etienne Baudoux
2023-09-19 16:19:04 +02:00
parent 823c177d96
commit 15b7ac745d
8 changed files with 146 additions and 46 deletions

View File

@@ -117,12 +117,13 @@ class TestLocationOrderpointCommon(SavepointCase):
return move return move
@classmethod @classmethod
def _create_outgoing_move(cls, qty, location=None): def _create_outgoing_move(cls, qty, location=None, defaults=None):
move = cls._create_move( move = cls._create_move(
"Delivery", "Delivery",
qty, qty,
location or cls.location_dest, location or cls.location_dest,
cls.env.ref("stock.stock_location_customers"), cls.env.ref("stock.stock_location_customers"),
defaults=defaults,
) )
move._action_assign() move._action_assign()
return move return move

View File

@@ -1,11 +1,12 @@
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de> # Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{ {
"name": "stock_location_orderpoint_source_relocate", "name": "stock_location_orderpoint_source_relocate",
"author": "MT Software, Odoo Community Association (OCA)", "author": "MT Software, BCIM, Odoo Community Association (OCA)",
"summary": "Run an auto location orderpoint replenishment " "summary": "Run an auto location orderpoint replenishment "
"also after a move gets relocated by Stock Move Source Relocate", "after the move relocation done by Stock Move Source Relocate",
"version": "14.0.1.0.2", "version": "14.0.1.0.2",
"development_status": "Alpha", "development_status": "Alpha",
"data": [], "data": [],
@@ -14,6 +15,6 @@
"stock_move_source_relocate", "stock_move_source_relocate",
], ],
"license": "AGPL-3", "license": "AGPL-3",
"maintainers": ["mt-software-de"], "maintainers": ["mt-software-de", "jbaudoux"],
"website": "https://github.com/OCA/stock-logistics-warehouse", "website": "https://github.com/OCA/stock-logistics-warehouse",
} }

View File

@@ -1 +1,2 @@
from . import stock_location_orderpoint
from . import stock_move from . import stock_move

View File

@@ -0,0 +1,43 @@
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models
from odoo.tools import float_round
class StockLocationOrderpoint(models.Model):
_inherit = "stock.location.orderpoint"
@api.model
def _compute_quantities_dict(self, locations, products):
qties = super()._compute_quantities_dict(locations, products)
# With the source relocation, we could have stock on the location that
# is reserved by moves with a source location on the parent location.
# Those moves are not considered by the standard virtual available
# stock.
Move = self.env["stock.move"].with_context(active_test=False)
for location, location_dict in qties.items():
products = products.with_context(location=location.id)
_, _, domain_move_out_loc = products._get_domain_locations()
domain_move_out_loc_todo = [
(
"state",
"in",
("waiting", "confirmed", "assigned", "partially_available"),
)
] + domain_move_out_loc
for product, qty in location_dict.items():
moves = Move.search(
domain_move_out_loc_todo + [("product_id", "=", product.id)],
order="id",
)
rounding = product.uom_id.rounding
unreserved_availability = float_round(
qty["outgoing_qty"] - sum(m.reserved_availability for m in moves),
precision_rounding=rounding,
)
qty["virtual_available"] = float_round(
qty["free_qty"] + qty["incoming_qty"] - unreserved_availability,
precision_rounding=rounding,
)
return qties

View File

@@ -1,4 +1,5 @@
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de> # Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models from odoo import models
@@ -6,17 +7,12 @@ from odoo import models
class StockMove(models.Model): class StockMove(models.Model):
_inherit = "stock.move" _inherit = "stock.move"
def _action_assign(self, *args, **kwargs): def _action_assign(self):
self = self.with_context(skip_auto_replenishment=True) self = self.with_context(skip_auto_replenishment=True)
res = super()._action_assign(*args, **kwargs) super()._action_assign()
self = self.with_context(skip_auto_replenishment=False)
return res
def _apply_source_relocate_rule(self, *args, **kwargs): def _apply_source_relocate(self):
relocated = super()._apply_source_relocate_rule(*args, **kwargs) res = super()._apply_source_relocate()
if not relocated: res = res.with_context(skip_auto_replenishment=False)
return relocated res._prepare_auto_replenishment_for_waiting_moves()
relocated.with_context( return res
skip_auto_replenishment=False
)._prepare_auto_replenishment_for_waiting_moves()
return relocated

View File

@@ -1 +1,2 @@
* Michael Tietz (MT Software) <mtietz@mt-software.de> * Michael Tietz (MT Software) <mtietz@mt-software.de>
* Jacques-Etienne Baudoux (BCIM) <je@bcim.be>

View File

@@ -1 +1 @@
Run an auto location orderpoint replenishment also after a move gets relocated by Stock Move Source Relocate Run the auto location orderpoint replenishment after the potiential move relocation by Stock Move Source Relocate

View File

@@ -11,48 +11,52 @@ from odoo.addons.stock_move_source_relocate.tests.common import SourceRelocateCo
class TestLocationOrderpoint(TestLocationOrderpointCommon, SourceRelocateCommon): class TestLocationOrderpoint(TestLocationOrderpointCommon, SourceRelocateCommon):
def test_auto_replenishment(self): @classmethod
def setUpClass(cls):
super().setUpClass()
name = "Internal Replenishment" name = "Internal Replenishment"
replenishment_location = self.env["stock.location"].create( cls.replenishment_location = cls.env["stock.location"].create(
{ {
"name": name, "name": name,
"location_id": self.wh.lot_stock_id.location_id.id, "location_id": cls.wh.lot_stock_id.location_id.id,
} }
) )
internal_location = replenishment_location.create( cls.internal_location = cls.env["stock.location"].create(
{ {
"name": name, "name": name,
"location_id": self.wh.lot_stock_id.id, "location_id": cls.wh.lot_stock_id.id,
} }
) )
picking_type = self._create_picking_type( picking_type = cls._create_picking_type(
name, replenishment_location, internal_location, self.wh name, cls.replenishment_location, cls.internal_location, cls.wh
) )
route = self._create_route( route = cls._create_route(
name, picking_type, replenishment_location, internal_location, self.wh name,
picking_type,
cls.replenishment_location,
cls.internal_location,
cls.wh,
) )
cls.orderpoint = Form(cls.env["stock.location.orderpoint"])
cls.orderpoint.location_id = cls.internal_location
cls.orderpoint.route_id = route
cls.orderpoint = cls.orderpoint.save()
orderpoint = Form(self.env["stock.location.orderpoint"]) cls._create_incoming_move(10, cls.replenishment_location)
orderpoint.location_id = internal_location cls.job_func = cls.env["stock.location.orderpoint"].run_auto_replenishment
orderpoint.route_id = route
orderpoint = orderpoint.save()
job_func = self.env["stock.location.orderpoint"].run_auto_replenishment def test_auto_replenishment_without_relocation(self):
self._create_incoming_move(10, replenishment_location)
self._create_relocate_rule(
self.wh.lot_stock_id, internal_location, self.wh.out_type_id
)
with trap_jobs() as trap: with trap_jobs() as trap:
move = self._create_outgoing_move(10, self.wh.lot_stock_id) move = self._create_outgoing_move(
move.picking_type_id = self.wh.out_type_id.id 10,
move._assign_picking() self.internal_location,
move._action_assign() defaults={"picking_type_id": self.wh.out_type_id.id},
self.assertEqual(move.location_id, internal_location) )
trap.assert_jobs_count(1, only=job_func) self.assertEqual(move.location_id, self.internal_location)
trap.assert_jobs_count(1, only=self.job_func)
trap.assert_enqueued_job( trap.assert_enqueued_job(
job_func, self.job_func,
args=(move.product_id, internal_location, "location_id"), args=(move.product_id, self.internal_location, "location_id"),
kwargs={}, kwargs={},
properties=dict( properties=dict(
identity_key=identity_exact, identity_key=identity_exact,
@@ -61,5 +65,58 @@ class TestLocationOrderpoint(TestLocationOrderpointCommon, SourceRelocateCommon)
self.product.invalidate_cache() self.product.invalidate_cache()
trap.perform_enqueued_jobs() trap.perform_enqueued_jobs()
replenish_move = self._get_replenishment_move(orderpoint) replenish_move = self._get_replenishment_move(self.orderpoint)
self._check_replenishment_move(replenish_move, 10, orderpoint) self._check_replenishment_move(replenish_move, 10, self.orderpoint)
def test_auto_replenishment_with_relocation(self):
self._create_relocate_rule(
self.wh.lot_stock_id, self.internal_location, self.wh.out_type_id
)
with trap_jobs() as trap:
move = self._create_outgoing_move(
10,
self.wh.lot_stock_id,
defaults={"picking_type_id": self.wh.out_type_id.id},
)
self.assertEqual(move.location_id, self.internal_location)
trap.assert_jobs_count(1, only=self.job_func)
trap.assert_enqueued_job(
self.job_func,
args=(move.product_id, self.internal_location, "location_id"),
kwargs={},
properties=dict(
identity_key=identity_exact,
),
)
self.product.invalidate_cache()
trap.perform_enqueued_jobs()
replenish_move = self._get_replenishment_move(self.orderpoint)
self._check_replenishment_move(replenish_move, 10, self.orderpoint)
def test_auto_replenishment_with_partial_relocation(self):
self._create_relocate_rule(
self.wh.lot_stock_id, self.internal_location, self.wh.out_type_id
)
self._create_quants(self.product, self.internal_location, 1)
with trap_jobs() as trap:
move = self._create_outgoing_move(
10,
self.wh.lot_stock_id,
defaults={"picking_type_id": self.wh.out_type_id.id},
)
self.assertEqual(move.location_id, self.wh.lot_stock_id)
trap.assert_jobs_count(1, only=self.job_func)
trap.assert_enqueued_job(
self.job_func,
args=(move.product_id, self.internal_location, "location_id"),
kwargs={},
properties=dict(
identity_key=identity_exact,
),
)
self.product.invalidate_cache()
trap.perform_enqueued_jobs()
replenish_move = self._get_replenishment_move(self.orderpoint)
self._check_replenishment_move(replenish_move, 9, self.orderpoint)