From fe4650d885e8ee002ab851dd26392d487fb31cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 17 Mar 2020 23:35:47 +0100 Subject: [PATCH] Define lost and found location for inventory on parent location We remove the lost and found location on the inventory to only used one define on the warehouse or parent of the inventoried location. issue9071 review276901002 --- CHANGELOG | 1 + doc/index.rst | 20 ++++++++--------- inventory.py | 22 ++++++++----------- location.py | 17 ++++++++++++++ location.xml | 9 ++++---- message.xml | 3 +++ tests/scenario_stock_inventory.rst | 5 +++-- ...cenario_stock_inventory_empty_quantity.rst | 3 ++- view/inventory_form.xml | 6 +++-- view/location_form.xml | 5 +++++ 10 files changed, 59 insertions(+), 32 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 53c3289af6..d71e28c569 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Define one lost and found location per warehouse * Show warehouse in user status if not unique * Add cost price revision * Add wizard to modify product cost price diff --git a/doc/index.rst b/doc/index.rst index b0dd2f95b1..f7a89f4db2 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,13 +18,14 @@ virtually stored. The following location types are defined: * Warehouse - Warehouses are meta-locations which define input, storage, picking and output - locations. These locations are all of type Storage. Input and Output are - locations where incoming an outgoing product are temporally stored awaiting - transportation. The storage location is often the biggest location where - products are stored for middle or long periods of time. The picking location - is optionally where the products are picked by the customer shipment - otherwise the storage location is used. + Warehouses are meta-locations which define input, storage, picking, output + and lost and found locations. These locations are all of type Storage. Input + and Output are locations where incoming an outgoing product are temporally + stored awaiting transportation. The storage location is often the biggest + location where products are stored for middle or long periods of time. The + picking location is optionally where the products are picked by the customer + shipment otherwise the storage location is used. The lost and found location + is the location used by inventories when correcting stock levels. * Customer @@ -261,9 +262,8 @@ of moves. It can be in one of these states: Inventory ********* -Inventories allow to control and update stock levels. They are mainly -composed of two locations (a Storage location and a Lost And Found -location), and a list of inventory lines. +Inventories allow to control and update stock levels. They are mainly composed +of the inventoried storage location and a list of inventory lines. Inventory lines consist of a product and it's default unit of measure, an expected quantity and the real quantity (the real products on the shelves). diff --git a/inventory.py b/inventory.py index 4ddaf8ba41..1713a24930 100644 --- a/inventory.py +++ b/inventory.py @@ -37,11 +37,6 @@ class Inventory(Workflow, ModelSQL, ModelView): }, depends=['state'], help="The date of the stock count.") - lost_found = fields.Many2One( - 'stock.location', 'Lost and Found', required=True, - domain=[('type', '=', 'lost_found')], states=_states, depends=_depends, - help="Used for the balancing entries needed when the stock is " - "corrected.") lines = fields.One2Many( 'stock.inventory.line', 'inventory', 'Lines', states={ @@ -106,6 +101,9 @@ class Inventory(Workflow, ModelSQL, ModelView): # Add index on create_date table.index_action('create_date', action='add') + # Migration from 5.4: remove lost_found + table.not_null_action('lost_found', 'remove') + @staticmethod def default_state(): return 'draft' @@ -119,13 +117,6 @@ class Inventory(Workflow, ModelSQL, ModelView): def default_company(): return Transaction().context.get('company') - @classmethod - def default_lost_found(cls): - Location = Pool().get('stock.location') - locations = Location.search(cls.lost_found.domain) - if len(locations) == 1: - return locations[0].id - @classmethod def view_attributes(cls): return [ @@ -446,7 +437,12 @@ class InventoryLine(ModelSQL, ModelView): if delta_qty == 0.0: return from_location = self.inventory.location - to_location = self.inventory.lost_found + to_location = self.inventory.location.lost_found_used + if not to_location: + raise InventoryValidationError( + gettext('stock.msg_inventory_location_missing_lost_found', + inventory=self.inventory.rec_name, + location=self.inventory.location.rec_name)) if delta_qty < 0: (from_location, to_location, delta_qty) = \ (to_location, from_location, -delta_qty) diff --git a/location.py b/location.py index 6768952d42..aee68eb76d 100644 --- a/location.py +++ b/location.py @@ -112,6 +112,18 @@ class Location(DeactivableMixin, tree(), ModelSQL, ModelView): depends=['type', 'storage_location'], help="Where stock is picked from.\n" "Leave empty to use the storage location.") + lost_found_location = fields.Many2One( + 'stock.location', "Lost and Found", + states={ + 'invisible': Eval('type') != 'warehouse', + 'readonly': ~Eval('active'), + }, + domain=[ + ('type', '=', 'lost_found'), + ], + depends=['type', 'active'], + help="Used, by inventories, when correcting stock levels " + "in the warehouse.") quantity = fields.Function( fields.Float('Quantity', help="The amount of stock in the location."), @@ -304,6 +316,11 @@ class Location(DeactivableMixin, tree(), ModelSQL, ModelView): cls._default_warehouse_cache.set(None, warehouse) return warehouse + @property + def lost_found_used(self): + if self.warehouse: + return self.warehouse.lost_found_location + @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): diff --git a/location.xml b/location.xml index 9e8a91563d..e60ad7c2f9 100644 --- a/location.xml +++ b/location.xml @@ -194,6 +194,10 @@ this repository contains the full copyright notices and license terms. --> STO storage + + Lost and Found + lost_found + Warehouse WH @@ -201,6 +205,7 @@ this repository contains the full copyright notices and license terms. --> + Supplier @@ -212,10 +217,6 @@ this repository contains the full copyright notices and license terms. --> CUS customer - - Lost and Found - lost_found - Transit storage diff --git a/message.xml b/message.xml index 423ba029b2..ff6d8fa822 100644 --- a/message.xml +++ b/message.xml @@ -41,6 +41,9 @@ because the type does not support moves and location has existing moves. To confirm the inventory "%(inventory)s" you must select an option for empty quantity. + + To confirm the inventory "%(inventory)s" you must set a lost and found on a parent of location "%(location)s". + Inventory line "%(line)s" is not unique on inventory "%(inventory)s". diff --git a/tests/scenario_stock_inventory.rst b/tests/scenario_stock_inventory.rst index 4a0773ff0f..0c809a0f24 100644 --- a/tests/scenario_stock_inventory.rst +++ b/tests/scenario_stock_inventory.rst @@ -28,6 +28,7 @@ Get stock locations:: >>> supplier_loc, = Location.find([('code', '=', 'SUP')]) >>> storage_loc, = Location.find([('code', '=', 'STO')]) >>> customer_loc, = Location.find([('code', '=', 'CUS')]) + >>> lost_found_loc, = Location.find([('type', '=', 'lost_found')]) Create products:: @@ -170,7 +171,7 @@ Confirm the inventory:: >>> move, = line_p1.moves >>> move.quantity 1.0 - >>> move.from_location == inventory.lost_found + >>> move.from_location == lost_found_loc True >>> move.to_location == inventory.location True @@ -230,7 +231,7 @@ Add quantity of consumable product:: >>> move, = line.moves >>> move.quantity 5.0 - >>> move.from_location == inventory.lost_found + >>> move.from_location == lost_found_loc True >>> move.to_location == inventory.location True diff --git a/tests/scenario_stock_inventory_empty_quantity.rst b/tests/scenario_stock_inventory_empty_quantity.rst index efd941e450..ab1f8e40b6 100644 --- a/tests/scenario_stock_inventory_empty_quantity.rst +++ b/tests/scenario_stock_inventory_empty_quantity.rst @@ -28,6 +28,7 @@ Get stock locations:: >>> supplier_loc, = Location.find([('code', '=', 'SUP')]) >>> storage_loc, = Location.find([('code', '=', 'STO')]) >>> customer_loc, = Location.find([('code', '=', 'CUS')]) + >>> lost_found_loc, = Location.find([('type', '=', 'lost_found')]) Create product:: @@ -83,5 +84,5 @@ Creating an inventory with empty quantities creates and empty move:: 1.0 >>> move.from_location == inventory.location True - >>> move.to_location == inventory.lost_found + >>> move.to_location == lost_found_loc True diff --git a/view/inventory_form.xml b/view/inventory_form.xml index 3dae6e5f2f..b4d7daa110 100644 --- a/view/inventory_form.xml +++ b/view/inventory_form.xml @@ -6,15 +6,17 @@ this repository contains the full copyright notices and license terms. -->