mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[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:
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
from . import stock_location_orderpoint
|
||||||
from . import stock_move
|
from . import stock_move
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user