Skip to content

Commit

Permalink
Merge PR OCA#164 into 13.0
Browse files Browse the repository at this point in the history
Signed-off-by guewen
  • Loading branch information
OCA-git-bot committed Feb 11, 2021
2 parents d5124a2 + 55970e2 commit 7778926
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 22 deletions.
64 changes: 43 additions & 21 deletions stock_storage_type/models/stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ class StockLocation(models.Model):
compute="_compute_location_is_empty",
store=True,
help="technical field: True if the location is empty "
"and there is no pending incoming products in the location",
"and there is no pending incoming products in the location. "
" Computed only if the location needs to check for emptiness "
'(has an "only empty" location storage type).',
)

in_move_ids = fields.One2many(
Expand Down Expand Up @@ -143,14 +145,20 @@ def _compute_leaf_location_ids(self):

def _should_compute_will_contain_product_ids(self):
return self.usage == "internal" and any(
location.do_not_mix_products
for location in self.allowed_location_storage_type_ids
storage_type.do_not_mix_products
for storage_type in self.allowed_location_storage_type_ids
)

def _should_compute_will_contain_lot_ids(self):
return self.usage == "internal" and any(
location.do_not_mix_lots
for location in self.allowed_location_storage_type_ids
storage_type.do_not_mix_lots
for storage_type in self.allowed_location_storage_type_ids
)

def _should_compute_location_is_empty(self):
return self.usage == "internal" and any(
storage_type.only_empty
for storage_type in self.allowed_location_storage_type_ids
)

@api.depends(
Expand All @@ -161,13 +169,16 @@ def _should_compute_will_contain_lot_ids(self):
)
def _compute_location_will_contain_product_ids(self):
for rec in self:
products = self.env["product.product"].browse()
if rec._should_compute_will_contain_product_ids():
products = (
rec.mapped("quant_ids.product_id")
| rec.mapped("in_move_ids.product_id")
| rec.mapped("in_move_line_ids.product_id")
)
if not rec._should_compute_will_contain_product_ids():
if rec.location_will_contain_product_ids:
no_product = self.env["product.product"].browse()
rec.location_will_contain_product_ids = no_product
continue
products = (
rec.mapped("quant_ids.product_id")
| rec.mapped("in_move_ids.product_id")
| rec.mapped("in_move_line_ids.product_id")
)
rec.location_will_contain_product_ids = products

@api.depends(
Expand All @@ -177,26 +188,37 @@ def _compute_location_will_contain_product_ids(self):
)
def _compute_location_will_contain_lot_ids(self):
for rec in self:
lots = self.env["stock.production.lot"].browse()
if rec._should_compute_will_contain_lot_ids():
lots = rec.mapped("quant_ids.lot_id") | rec.mapped(
"in_move_line_ids.lot_id"
)
if not rec._should_compute_will_contain_lot_ids():
if rec.location_will_contain_lot_ids:
no_lot = self.env["stock.production.lot"].browse()
rec.location_will_contain_lot_ids = no_lot
continue
lots = rec.mapped("quant_ids.lot_id") | rec.mapped(
"in_move_line_ids.lot_id"
)
rec.location_will_contain_lot_ids = lots

@api.depends(
"quant_ids.quantity",
"out_move_line_ids.qty_done",
"in_move_ids",
"in_move_line_ids",
"allowed_location_storage_type_ids.only_empty",
)
def _compute_location_is_empty(self):
for rec in self:
if rec.usage != "internal":
# No restriction should apply on customer/supplier/...
# locations.
rec.location_is_empty = True
# No restriction should apply on customer/supplier/...
# locations and we don't need to compute is empty
# if there is no limit on the location
if not rec._should_compute_location_is_empty():
# avoid write if not required
if not rec.location_is_empty:
rec.location_is_empty = True
continue
# we do want to keep a write here even if the value is the same
# to enforce concurrent transaction safety: 2 moves taking
# quantities in a location have to be executed sequentially
# or the location could remain "not empty"
if (
sum(rec.quant_ids.mapped("quantity"))
- sum(rec.out_move_line_ids.mapped("qty_done"))
Expand Down
22 changes: 22 additions & 0 deletions stock_storage_type/tests/test_stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,25 @@ def test_will_contain_lot_ids(self):
location.location_will_contain_lot_ids,
self.env["stock.production.lot"].browse(),
)

def test_location_is_empty_non_internal(self):
location = self.env.ref("stock.stock_location_customers")
# we always consider an non-internal location empty, the put-away
# rules do not apply and we can add as many quants as we want
self.assertTrue(location.location_is_empty)
self._update_qty_in_location(location, self.product, 10)
self.assertTrue(location.location_is_empty)

def test_location_is_empty(self):
location = self.pallets_reserve_bin_1_location
self.assertTrue(location.allowed_location_storage_type_ids.only_empty)
self.assertTrue(location.location_is_empty)
self._update_qty_in_location(location, self.product, 10)
self.assertFalse(location.location_is_empty)

# When the location has no "only_empty" storage type, we don't
# care about if it is empty or not, we keep it as True so we
# can always put things inside. Not computing it prevents
# useless race conditions on concurrent writes.
location.allowed_location_storage_type_ids.only_empty = False
self.assertTrue(location.location_is_empty)
30 changes: 29 additions & 1 deletion stock_storage_type_buffer/models/stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,42 @@

import logging

from odoo import models
from odoo import api, fields, models

_logger = logging.getLogger(__name__)


class StockLocation(models.Model):
_inherit = "stock.location"

storage_buffer_ids = fields.Many2many(
comodel_name="stock.location.storage.buffer",
relation="stock_location_storage_buffer_stock_location_buffer_rel",
)
is_in_storage_buffer = fields.Boolean(
compute="_compute_is_in_storage_buffer", store=True,
)

@api.depends("storage_buffer_ids", "location_id.is_in_storage_buffer")
def _compute_is_in_storage_buffer(self):
for location in self:
if self.storage_buffer_ids:
location.is_in_storage_buffer = True
else:
location.is_in_storage_buffer = (
location.location_id.is_in_storage_buffer
)

def _should_compute_location_is_empty(self):
if super()._should_compute_location_is_empty():
return True
return self.is_in_storage_buffer

# add dependency
@api.depends("is_in_storage_buffer")
def _compute_location_is_empty(self):
super()._compute_location_is_empty()

def _select_final_valid_putaway_locations(self, limit=None):
"""Return the valid locations using the provided limit
Expand Down
33 changes: 33 additions & 0 deletions stock_storage_type_buffer/tests/test_storage_type_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,39 @@ def _create_pallet_move_to_putaway(self):
)
return move

def test_location_field_is_in_storage_buffer(self):
self.assertTrue(self.buffer_location.is_in_storage_buffer)

area = self.env["stock.location"].create(
{"name": "Pallet Area", "location_id": self.warehouse.lot_stock_id.id}
)
self.assertFalse(area.is_in_storage_buffer)
leaf = self.env["stock.location"].create(
{"name": "Pallet Leaf", "location_id": area.id}
)
self.assertFalse(area.is_in_storage_buffer)

area.location_id = self.buffer_location.id
self.assertTrue(area.is_in_storage_buffer)
self.assertTrue(leaf.is_in_storage_buffer)

self.storage_buffer.unlink()
self.assertFalse(self.buffer_location.is_in_storage_buffer)
self.assertFalse(area.is_in_storage_buffer)
self.assertFalse(leaf.is_in_storage_buffer)

def test_location_field_is_empty(self):
"""Do not compute "location_is_empty" if there is no buffer"""
# we have a buffer on the location: compute location_is_empty
self.assertTrue(self.buffer_location.location_is_empty)
self._update_qty_in_location(self.buffer_location, self.product, 1)
self.assertFalse(self.buffer_location.location_is_empty)
self.storage_buffer.unlink()
# when we have no buffer, we don't care about the field
# "location_is_empty", we prefer not to compute it to prevent
# concurrent writes
self.assertTrue(self.buffer_location.location_is_empty)

def test_buffer_with_capacity(self):
"""The buffer is empty so we can move to the pallet locations"""
move = self._create_pallet_move_to_putaway()
Expand Down

0 comments on commit 7778926

Please sign in to comment.