Skip to content

Commit

Permalink
[FIX] stock_location_orderpoint
Browse files Browse the repository at this point in the history
Check orderpoints also for non relocated moves
Fix quantity to replenish in case of partial source relocation
  • Loading branch information
jbaudoux committed Oct 4, 2023
1 parent 823c177 commit 15b7ac7
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 43 deletions.
3 changes: 2 additions & 1 deletion stock_location_orderpoint/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,13 @@ def _create_incoming_move(cls, qty, location):
return move

@classmethod
def _create_outgoing_move(cls, qty, location=None):
def _create_outgoing_move(cls, qty, location=None, defaults=None):
move = cls._create_move(
"Delivery",
qty,
location or cls.location_dest,
cls.env.ref("stock.stock_location_customers"),
defaults=defaults,
)
move._action_assign()
return move
Expand Down
7 changes: 4 additions & 3 deletions stock_location_orderpoint_source_relocate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"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 "
"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",
"development_status": "Alpha",
"data": [],
Expand All @@ -14,6 +15,6 @@
"stock_move_source_relocate",
],
"license": "AGPL-3",
"maintainers": ["mt-software-de"],
"maintainers": ["mt-software-de", "jbaudoux"],
"website": "https://github.com/OCA/stock-logistics-warehouse",
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import stock_location_orderpoint
from . import stock_move
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# 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
20 changes: 8 additions & 12 deletions stock_location_orderpoint_source_relocate/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models


class StockMove(models.Model):
_inherit = "stock.move"

def _action_assign(self, *args, **kwargs):
def _action_assign(self):
self = self.with_context(skip_auto_replenishment=True)
res = super()._action_assign(*args, **kwargs)
self = self.with_context(skip_auto_replenishment=False)
return res
super()._action_assign()

def _apply_source_relocate_rule(self, *args, **kwargs):
relocated = super()._apply_source_relocate_rule(*args, **kwargs)
if not relocated:
return relocated
relocated.with_context(
skip_auto_replenishment=False
)._prepare_auto_replenishment_for_waiting_moves()
return relocated
def _apply_source_relocate(self):
res = super()._apply_source_relocate()
res = res.with_context(skip_auto_replenishment=False)
res._prepare_auto_replenishment_for_waiting_moves()
return res
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* Michael Tietz (MT Software) <[email protected]>
* Jacques-Etienne Baudoux (BCIM) <[email protected]>
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,105 @@


class TestLocationOrderpoint(TestLocationOrderpointCommon, SourceRelocateCommon):
def test_auto_replenishment(self):
@classmethod
def setUpClass(cls):
super().setUpClass()
name = "Internal Replenishment"
replenishment_location = self.env["stock.location"].create(
cls.replenishment_location = cls.env["stock.location"].create(
{
"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,
"location_id": self.wh.lot_stock_id.id,
"location_id": cls.wh.lot_stock_id.id,
}
)
picking_type = self._create_picking_type(
name, replenishment_location, internal_location, self.wh
picking_type = cls._create_picking_type(
name, cls.replenishment_location, cls.internal_location, cls.wh
)
route = self._create_route(
name, picking_type, replenishment_location, internal_location, self.wh
route = cls._create_route(
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"])
orderpoint.location_id = internal_location
orderpoint.route_id = route
orderpoint = orderpoint.save()
cls._create_incoming_move(10, cls.replenishment_location)
cls.job_func = cls.env["stock.location.orderpoint"].run_auto_replenishment

job_func = self.env["stock.location.orderpoint"].run_auto_replenishment
def test_auto_replenishment_without_relocation(self):
with trap_jobs() as trap:
move = self._create_outgoing_move(
10,
self.internal_location,
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_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)

self._create_incoming_move(10, replenishment_location)
def test_auto_replenishment_with_partial_relocation(self):
self._create_relocate_rule(
self.wh.lot_stock_id, internal_location, self.wh.out_type_id
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)
move.picking_type_id = self.wh.out_type_id.id
move._assign_picking()
move._action_assign()
self.assertEqual(move.location_id, internal_location)
trap.assert_jobs_count(1, only=job_func)
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(
job_func,
args=(move.product_id, internal_location, "location_id"),
self.job_func,
args=(move.product_id, self.internal_location, "location_id"),
kwargs={},
properties=dict(
identity_key=identity_exact,
Expand All @@ -61,5 +118,5 @@ def test_auto_replenishment(self):

self.product.invalidate_cache()
trap.perform_enqueued_jobs()
replenish_move = self._get_replenishment_move(orderpoint)
self._check_replenishment_move(replenish_move, 10, orderpoint)
replenish_move = self._get_replenishment_move(self.orderpoint)
self._check_replenishment_move(replenish_move, 9, self.orderpoint)

0 comments on commit 15b7ac7

Please sign in to comment.