Skip to content

Commit

Permalink
Add compatibility between dynamic routing and source relocate
Browse files Browse the repository at this point in the history
  • Loading branch information
guewen committed Jul 3, 2020
1 parent db4bd2e commit 2b95709
Show file tree
Hide file tree
Showing 16 changed files with 255 additions and 38 deletions.
6 changes: 6 additions & 0 deletions setup/stock_move_source_relocate_dynamic_routing/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
63 changes: 36 additions & 27 deletions stock_move_source_relocate/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,31 +30,40 @@ def _apply_source_relocate(self):
relocation = self.env["stock.source.relocate"]._rule_for_move(move)
if not relocation or relocation.relocate_location_id == move.location_id:
continue
move._apply_source_relocate_rule(
relocation, reserved_availability, roundings
)

rounding = roundings[move]
if not reserved_availability[move]:
# nothing could be reserved, however, we want to source the
# move on the specific relocation (for replenishment), so
# update it's source location
move.location_id = relocation.relocate_location_id
else:
missing_reserved_uom_quantity = (
move.product_uom_qty - reserved_availability[move]
)
need = move.product_uom._compute_quantity(
missing_reserved_uom_quantity,
move.product_id.uom_id,
rounding_method="HALF-UP",
)

if float_is_zero(need, precision_rounding=rounding):
continue

# A part of the quantity could be reserved in the original
# location, so keep this part in the move and split the rest
# in a new move, where will take the goods in the relocation
new_move_id = self._split(need)
# recheck first move which should now be available
new_move = self.browse(new_move_id)
new_move.location_id = relocation.relocate_location_id
move._action_assign()
def _apply_source_relocate_rule(self, relocation, reserved_availability, roundings):
relocated = self.env["stock.move"].browse()

rounding = roundings[self]
if not reserved_availability[self]:
# nothing could be reserved, however, we want to source the
# move on the specific relocation (for replenishment), so
# update it's source location
self.location_id = relocation.relocate_location_id
relocated = self
else:
missing_reserved_uom_quantity = (
self.product_uom_qty - reserved_availability[self]
)
need = self.product_uom._compute_quantity(
missing_reserved_uom_quantity,
self.product_id.uom_id,
rounding_method="HALF-UP",
)

if float_is_zero(need, precision_rounding=rounding):
return relocated

# A part of the quantity could be reserved in the original
# location, so keep this part in the move and split the rest
# in a new move, where will take the goods in the relocation
new_move_id = self._split(need)
# recheck first move which should now be available
new_move = self.browse(new_move_id)
new_move.location_id = relocation.relocate_location_id
self._action_assign()
relocated = new_move
return relocated
24 changes: 13 additions & 11 deletions stock_move_source_relocate/tests/test_source_relocate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from odoo.tests import common


class TestSourceRelocate(common.SavepointCase):
class SourceRelocateCommon(common.SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
Expand Down Expand Up @@ -53,9 +53,21 @@ def _create_single_move(self, product, picking_type):
}
return self.env["stock.move"].create(move_vals)

def _create_relocate_rule(self, location, relocation, picking_type, domain=None):
self.env["stock.source.relocate"].create(
{
"location_id": location.id,
"picking_type_id": picking_type.id,
"relocate_location_id": relocation.id,
"rule_domain": domain or "[]",
}
)

def _update_qty_in_location(self, location, product, quantity):
self.env["stock.quant"]._update_available_quantity(product, location, quantity)


class TestSourceRelocate(SourceRelocateCommon):
def test_relocate_child_of_location(self):
# relocate location is a child, valid
self.env["stock.source.relocate"].create(
Expand All @@ -77,16 +89,6 @@ def test_relocate_not_child_of_location(self):
}
)

def _create_relocate_rule(self, location, relocation, picking_type, domain=None):
self.env["stock.source.relocate"].create(
{
"location_id": location.id,
"picking_type_id": picking_type.id,
"relocate_location_id": relocation.id,
"rule_domain": domain or "[]",
}
)

def test_relocate_whole_move(self):
self._create_relocate_rule(
self.wh.lot_stock_id, self.loc_replenish, self.wh.pick_type_id
Expand Down
1 change: 1 addition & 0 deletions stock_move_source_relocate_dynamic_routing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
16 changes: 16 additions & 0 deletions stock_move_source_relocate_dynamic_routing/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2019 Camptocamp (https://www.camptocamp.com)
{
"name": "Stock Source Relocate - Dynamic Routing",
"summary": "Glue module",
"author": "Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"category": "Warehouse Management",
"version": "13.0.1.0.0",
"license": "AGPL-3",
"depends": ["stock_dynamic_routing", "stock_move_source_relocate"],
"demo": [],
"data": ["views/stock_routing_views.xml", "views/stock_source_relocate_views.xml"],
"auto_install": True,
"installable": True,
"development_status": "Alpha",
}
3 changes: 3 additions & 0 deletions stock_move_source_relocate_dynamic_routing/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import stock_move
from . import stock_source_relocate
from . import stock_routing
21 changes: 21 additions & 0 deletions stock_move_source_relocate_dynamic_routing/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)

from odoo import models


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

def _apply_source_relocate_rule(self, relocation, reserved_availability, roundings):
relocated = super(
StockMove,
# disable application of routing in write() method of
# stock_dynamic_routing, we'll apply it here whatever the state of
# the move is
self.with_context(__applying_routing_rule=True),
)._apply_source_relocate_rule(relocation, reserved_availability, roundings)
# restore the previous context without "__applying_routing_rule", otherwise
# it wouldn't properly apply the routing in chain in the further moves
relocated.with_context(self.env.context)._chain_apply_routing()
return relocated
28 changes: 28 additions & 0 deletions stock_move_source_relocate_dynamic_routing/models/stock_routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import _, models


class StockRouting(models.Model):

_inherit = "stock.routing"

def action_view_source_relocate(self):
picking_types = self.mapped("picking_type_id")
routing = self.env["stock.routing"].search(
[("picking_type_id", "in", picking_types.ids)]
)
context = self.env.context
if len(picking_types) == 1:
context = dict(context, default_picking_type_id=picking_types.id)
return {
"name": _("Source Relocation"),
"domain": [("id", "in", routing.ids)],
"res_model": "stock.source.relocate",
"type": "ir.actions.act_window",
"view_id": False,
"view_mode": "tree,form",
"limit": 20,
"context": context,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import _, models


class StockSourceRelocate(models.Model):

_inherit = "stock.source.relocate"

def action_view_dynamic_routing(self):
picking_types = self.mapped("picking_type_id")
routing = self.env["stock.routing"].search(
[("picking_type_id", "in", picking_types.ids)]
)
context = self.env.context
if len(picking_types) == 1:
context = dict(context, default_picking_type_id=picking_types.id)
return {
"name": _("Dynamic Routing"),
"domain": [("id", "in", routing.ids)],
"res_model": "stock.routing",
"type": "ir.actions.act_window",
"view_id": False,
"view_mode": "tree,form",
"limit": 20,
"context": context,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Glue module between ``stock_move_source_relocate`` and
``stock_dynamic_routing``.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_dynamic_relocate
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from odoo.addons.stock_move_source_relocate.tests.test_source_relocate import (
SourceRelocateCommon,
)


class TestSourceRelocate(SourceRelocateCommon):
def test_relocate_with_routing(self):
"""Check that routing is applied when a relocation happen"""
# Relocation: for unavailable move in Stock, relocate to Replenish
self._create_relocate_rule(
self.wh.lot_stock_id, self.loc_replenish, self.wh.pick_type_id
)
# Routing: a move with source location in replenishment is classified
# in picking type Replenish
pick_type_replenish = self.env["stock.picking.type"].create(
{
"name": "Replenish",
"code": "internal",
"sequence_code": "R",
"warehouse_id": self.wh.id,
"use_create_lots": False,
"use_existing_lots": True,
"default_location_src_id": self.loc_replenish.id,
"default_location_dest_id": self.wh.lot_stock_id.id,
}
)
self.env["stock.routing"].create(
{
"location_id": self.loc_replenish.id,
"picking_type_id": self.wh.pick_type_id.id,
"rule_ids": [
(
0,
0,
{"method": "pull", "picking_type_id": pick_type_replenish.id},
)
],
}
)
move = self._create_single_move(self.product, self.wh.pick_type_id)
move._assign_picking()
move._action_assign()
self.assertRecordValues(
move,
[
{
"state": "confirmed",
"product_qty": 10.0,
"reserved_availability": 0.0,
# routing changed the picking type
"picking_type_id": pick_type_replenish.id,
"location_id": self.loc_replenish.id,
}
],
)
# routing created a new move
self.assertTrue(move.move_dest_ids)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_routing_form" model="ir.ui.view">
<field name="name">stock.routing.form</field>
<field name="model">stock.routing</field>
<field name="inherit_id" ref="stock_dynamic_routing.view_stock_routing_form" />
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button
name="action_view_source_relocate"
string="Source Relocation"
icon="fa-refresh"
class="oe_stat_button"
type="object"
/>
</div>
</field>
</record>
</odoo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_source_relocate_form" model="ir.ui.view">
<field name="name">stock.source.relocate.form</field>
<field name="model">stock.source.relocate</field>
<field
name="inherit_id"
ref="stock_move_source_relocate.view_stock_source_relocate_form"
/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button
name="action_view_dynamic_routing"
string="Dynamic Routing"
icon="fa-refresh"
class="oe_stat_button"
type="object"
/>
</div>
</field>
</record>
</odoo>

0 comments on commit 2b95709

Please sign in to comment.