mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[9.0][IMP] stock_cycle_count: add the possibility to define rules for specific zones.
This commit is contained in:
@@ -18,6 +18,7 @@ can propose Zero-Confirmations which are simple and opportunistic counts to
|
|||||||
check whether a locations has actually became empty or not.
|
check whether a locations has actually became empty or not.
|
||||||
|
|
||||||
With this strategy it is possible to:
|
With this strategy it is possible to:
|
||||||
|
|
||||||
* Remove the need to perform full physical inventories and to stop the
|
* Remove the need to perform full physical inventories and to stop the
|
||||||
production in the warehouse.
|
production in the warehouse.
|
||||||
* Measure the accuracy of the inventory records and improve it.
|
* Measure the accuracy of the inventory records and improve it.
|
||||||
@@ -36,14 +37,12 @@ Configuration
|
|||||||
|
|
||||||
You can configure the rules to compute the cycle count, acting as follow:
|
You can configure the rules to compute the cycle count, acting as follow:
|
||||||
|
|
||||||
#. Go to "Inventory > Configuration > Cycle Count Rules"
|
#. Go to *Inventory > Configuration > Cycle Count Rules*.
|
||||||
#. Create as much cycle count rules as you want.
|
#. Create as much cycle count rules as you want.
|
||||||
#. Assign the rules to the Warehouse where you want to apply the rules in.
|
#. Assign the rules to the Warehouse or zones where you want to apply the rules
|
||||||
#. Set a "Cycle Count Planning Horizon" for each warehouse.
|
in.
|
||||||
|
#. Go to *Inventory > Configuration > Warehouse Management > Warehouses* and
|
||||||
.. figure:: path/to/local/image.png
|
set a *Cycle Count Planning Horizon* for each warehouse.
|
||||||
:alt: alternative description
|
|
||||||
:width: 600 px
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
=====
|
=====
|
||||||
@@ -53,11 +52,12 @@ is described below.
|
|||||||
|
|
||||||
#. Go to "Inventory > Configuration > Warehouse Management > Warehouses".
|
#. Go to "Inventory > Configuration > Warehouse Management > Warehouses".
|
||||||
#. Select all the warehouses you want to compute the rules in.
|
#. Select all the warehouses you want to compute the rules in.
|
||||||
#. Click on "Action" and then in "Compute Cycle Count Rules".
|
#. Click on "Action" and then in "Compute Cycle Count Rules". (**note**: A
|
||||||
|
cron job will do this for every warehouse daily.)
|
||||||
#. Go to "Inventory Control > Cycle Counts".
|
#. Go to "Inventory Control > Cycle Counts".
|
||||||
#. Select a Cycle Count planned an confirm it, this will create a draft
|
#. Select a planned Cycle Count and confirm it, this will create a draft
|
||||||
Inventory Adjustment.
|
Inventory Adjustment.
|
||||||
#. In the right top corner of the form view you can access the generated
|
#. In the right top corner of the form view you can access to the generated
|
||||||
Inventory Adjustment.
|
Inventory Adjustment.
|
||||||
#. Proceed with the Inventory Adjustment as usual.
|
#. Proceed with the Inventory Adjustment as usual.
|
||||||
|
|
||||||
@@ -65,9 +65,6 @@ is described below.
|
|||||||
:alt: Try me on Runbot
|
:alt: Try me on Runbot
|
||||||
:target: https://runbot.odoo-community.org/runbot/153/9.0
|
:target: https://runbot.odoo-community.org/runbot/153/9.0
|
||||||
|
|
||||||
.. repo_id is available in https://github.com/OCA/stock-logistics-warehouse
|
|
||||||
.. branch is "9.0" for example
|
|
||||||
|
|
||||||
|
|
||||||
Bug Tracker
|
Bug Tracker
|
||||||
===========
|
===========
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"name": "Stock Cycle Count",
|
"name": "Stock Cycle Count",
|
||||||
"summary": "Adds the capability to schedule cycle counts in a "
|
"summary": "Adds the capability to schedule cycle counts in a "
|
||||||
"warehouse through different rules defined by the user",
|
"warehouse through different rules defined by the user",
|
||||||
"version": "9.0.1.0.0",
|
"version": "9.0.1.1.0",
|
||||||
"author": "Eficent, "
|
"author": "Eficent, "
|
||||||
"Odoo Community Association (OCA)",
|
"Odoo Community Association (OCA)",
|
||||||
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from openerp import api, fields, models
|
|||||||
|
|
||||||
class StockCycleCount(models.Model):
|
class StockCycleCount(models.Model):
|
||||||
_name = 'stock.cycle.count'
|
_name = 'stock.cycle.count'
|
||||||
|
_description = "Stock Cycle Counts"
|
||||||
_inherit = 'mail.thread'
|
_inherit = 'mail.thread'
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from datetime import timedelta, datetime
|
|||||||
|
|
||||||
class StockCycleCountRule(models.Model):
|
class StockCycleCountRule(models.Model):
|
||||||
_name = 'stock.cycle.count.rule'
|
_name = 'stock.cycle.count.rule'
|
||||||
|
_description = "Stock Cycle Counts Rules"
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def _compute_currency(self):
|
def _compute_currency(self):
|
||||||
@@ -77,6 +78,15 @@ class StockCycleCountRule(models.Model):
|
|||||||
_('You cannot define a negative period.')
|
_('You cannot define a negative period.')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@api.onchange('location_ids')
|
||||||
|
def _get_warehouses(self):
|
||||||
|
"""Get the warehouses for the selected locations."""
|
||||||
|
wh_ids = []
|
||||||
|
for loc in self.location_ids:
|
||||||
|
wh_ids.append(loc.get_warehouse(loc))
|
||||||
|
wh_ids = list(set(wh_ids))
|
||||||
|
self.warehouse_ids = self.env['stock.warehouse'].browse(wh_ids)
|
||||||
|
|
||||||
name = fields.Char('Name', required=True)
|
name = fields.Char('Name', required=True)
|
||||||
rule_type = fields.Selection(selection="_selection_rule_types",
|
rule_type = fields.Selection(selection="_selection_rule_types",
|
||||||
string='Type of rule',
|
string='Type of rule',
|
||||||
@@ -94,11 +104,19 @@ class StockCycleCountRule(models.Model):
|
|||||||
compute=_compute_currency)
|
compute=_compute_currency)
|
||||||
accuracy_threshold = fields.Float(string='Minimum Accuracy Threshold',
|
accuracy_threshold = fields.Float(string='Minimum Accuracy Threshold',
|
||||||
digits=(3, 2))
|
digits=(3, 2))
|
||||||
warehouse_ids = fields.Many2many(comodel_name='stock.warehouse',
|
apply_in = fields.Selection(
|
||||||
relation='warehouse_cycle_count_rule_rel',
|
string='Apply this rule in:',
|
||||||
column1='rule_id',
|
selection=[('warehouse', 'Selected warehouses'),
|
||||||
column2='warehouse_id',
|
('location', 'Selected Location Zones.')],
|
||||||
string='Applied in')
|
default='warehouse')
|
||||||
|
warehouse_ids = fields.Many2many(
|
||||||
|
comodel_name='stock.warehouse',
|
||||||
|
relation='warehouse_cycle_count_rule_rel', column1='rule_id',
|
||||||
|
column2='warehouse_id', string='Warehouses where applied')
|
||||||
|
location_ids = fields.Many2many(
|
||||||
|
comodel_name='stock.location',
|
||||||
|
relation='location_cycle_count_rule_rel', column1='rule_id',
|
||||||
|
column2='location_id', string='Zones where applied')
|
||||||
|
|
||||||
def compute_rule(self, locs):
|
def compute_rule(self, locs):
|
||||||
if self.rule_type == 'periodic':
|
if self.rule_type == 'periodic':
|
||||||
|
|||||||
@@ -36,24 +36,30 @@ class StockWarehouse(models.Model):
|
|||||||
return date_horizon
|
return date_horizon
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_cycle_count_locations_search_domain(self):
|
def _get_cycle_count_locations_search_domain(
|
||||||
wh_parent_left = self.view_location_id.parent_left
|
self, parent):
|
||||||
wh_parent_right = self.view_location_id.parent_right
|
domain = [('parent_left', '>=', parent.parent_left),
|
||||||
domain = [('parent_left', '>', wh_parent_left),
|
('parent_right', '<=', parent.parent_right),
|
||||||
('parent_right', '<', wh_parent_right),
|
|
||||||
('cycle_count_disabled', '=', False)]
|
('cycle_count_disabled', '=', False)]
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _search_cycle_count_locations(self):
|
def _search_cycle_count_locations(self, rule):
|
||||||
locations = self.env['stock.location'].search(
|
locations = self.env['stock.location']
|
||||||
self._get_cycle_count_locations_search_domain())
|
if rule.apply_in == 'warehouse':
|
||||||
|
locations = self.env['stock.location'].search(
|
||||||
|
self._get_cycle_count_locations_search_domain(
|
||||||
|
self.view_location_id))
|
||||||
|
elif rule.apply_in == 'location':
|
||||||
|
for loc in rule.location_ids:
|
||||||
|
locations += self.env['stock.location'].search(
|
||||||
|
self._get_cycle_count_locations_search_domain(loc))
|
||||||
return locations
|
return locations
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _cycle_count_rules_to_compute(self):
|
def _cycle_count_rules_to_compute(self):
|
||||||
rules = self.cycle_count_rule_ids.search([
|
rules = self.cycle_count_rule_ids.search([
|
||||||
('rule_type', '!=', 'zero')])
|
('rule_type', '!=', 'zero'), ('warehouse_ids', '=', self.id)])
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
@@ -62,10 +68,10 @@ class StockWarehouse(models.Model):
|
|||||||
returns a list with required dates for the cycle count of each
|
returns a list with required dates for the cycle count of each
|
||||||
location '''
|
location '''
|
||||||
proposed_cycle_counts = []
|
proposed_cycle_counts = []
|
||||||
locations = self._search_cycle_count_locations()
|
|
||||||
rules = self._cycle_count_rules_to_compute()
|
rules = self._cycle_count_rules_to_compute()
|
||||||
if locations:
|
for rule in rules:
|
||||||
for rule in rules:
|
locations = self._search_cycle_count_locations(rule)
|
||||||
|
if locations:
|
||||||
proposed_cycle_counts.extend(rule.compute_rule(locations))
|
proposed_cycle_counts.extend(rule.compute_rule(locations))
|
||||||
if proposed_cycle_counts:
|
if proposed_cycle_counts:
|
||||||
locations = list(set([d['location'] for d in
|
locations = list(set([d['location'] for d in
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ class TestStockCycleCount(common.TransactionCase):
|
|||||||
self.user = self._create_user(
|
self.user = self._create_user(
|
||||||
'user_2', [self.g_stock_user], self.company).id
|
'user_2', [self.g_stock_user], self.company).id
|
||||||
|
|
||||||
|
# Create warehouses:
|
||||||
|
self.big_wh = self.stock_warehouse_model.create({
|
||||||
|
'name': 'BIG',
|
||||||
|
'code': 'B',
|
||||||
|
'cycle_count_planning_horizon': 30})
|
||||||
|
self.small_wh = self.stock_warehouse_model.create({
|
||||||
|
'name': 'SMALL', 'code': 'S'})
|
||||||
|
|
||||||
# Create rules:
|
# Create rules:
|
||||||
self.rule_periodic = \
|
self.rule_periodic = \
|
||||||
self._create_stock_cycle_count_rule_periodic(
|
self._create_stock_cycle_count_rule_periodic(
|
||||||
@@ -44,30 +52,26 @@ class TestStockCycleCount(common.TransactionCase):
|
|||||||
self.manager, 'rule_2', [100])
|
self.manager, 'rule_2', [100])
|
||||||
self.rule_accuracy = \
|
self.rule_accuracy = \
|
||||||
self._create_stock_cycle_count_rule_accuracy(
|
self._create_stock_cycle_count_rule_accuracy(
|
||||||
self.manager, 'rule_3', [5])
|
self.manager, 'rule_3', [5], self.big_wh.view_location_id.ids)
|
||||||
self.zero_rule = self._create_stock_cycle_count_rule_zero(
|
self.zero_rule = self._create_stock_cycle_count_rule_zero(
|
||||||
self.manager, 'rule_4')
|
self.manager, 'rule_4')
|
||||||
|
|
||||||
# Create and configure warehouses:
|
# Configure warehouses:
|
||||||
self.rule_ids = [
|
self.rule_ids = [
|
||||||
self.rule_periodic.id,
|
self.rule_periodic.id,
|
||||||
self.rule_turnover.id,
|
self.rule_turnover.id,
|
||||||
self.rule_accuracy.id,
|
self.rule_accuracy.id,
|
||||||
self.zero_rule.id]
|
self.zero_rule.id]
|
||||||
self.big_wh = self.stock_warehouse_model.create({
|
self.big_wh.write({
|
||||||
'name': 'BIG',
|
|
||||||
'code': 'B',
|
|
||||||
'cycle_count_planning_horizon': 30,
|
|
||||||
'cycle_count_rule_ids': [(6, 0, self.rule_ids)]
|
'cycle_count_rule_ids': [(6, 0, self.rule_ids)]
|
||||||
})
|
})
|
||||||
self.small_wh = self.stock_warehouse_model.create({
|
|
||||||
'name': 'SMALL', 'code': 'S'})
|
|
||||||
|
|
||||||
# Create a location:
|
# Create a location:
|
||||||
self.count_loc = self.stock_location_model.create({
|
self.count_loc = self.stock_location_model.create({
|
||||||
'name': 'Place',
|
'name': 'Place',
|
||||||
'usage': 'production'
|
'usage': 'production'
|
||||||
})
|
})
|
||||||
|
self.stock_location_model._parent_store_compute()
|
||||||
|
|
||||||
# Create a cycle count:
|
# Create a cycle count:
|
||||||
self.cycle_count_1 = self.cycle_count_model.sudo(self.manager).create({
|
self.cycle_count_1 = self.cycle_count_model.sudo(self.manager).create({
|
||||||
@@ -113,11 +117,14 @@ class TestStockCycleCount(common.TransactionCase):
|
|||||||
})
|
})
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
def _create_stock_cycle_count_rule_accuracy(self, uid, name, values):
|
def _create_stock_cycle_count_rule_accuracy(
|
||||||
|
self, uid, name, values, zone_ids):
|
||||||
rule = self.stock_cycle_count_rule_model.sudo(uid).create({
|
rule = self.stock_cycle_count_rule_model.sudo(uid).create({
|
||||||
'name': name,
|
'name': name,
|
||||||
'rule_type': 'accuracy',
|
'rule_type': 'accuracy',
|
||||||
'accuracy_threshold': values[0],
|
'accuracy_threshold': values[0],
|
||||||
|
'apply_in': 'location',
|
||||||
|
'location_ids': [(6, 0, zone_ids)],
|
||||||
})
|
})
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
@@ -132,8 +139,10 @@ class TestStockCycleCount(common.TransactionCase):
|
|||||||
"""Tests creation of cycle counts."""
|
"""Tests creation of cycle counts."""
|
||||||
# Common rules:
|
# Common rules:
|
||||||
wh = self.big_wh
|
wh = self.big_wh
|
||||||
self.stock_location_model._parent_store_compute()
|
locs = self.stock_location_model
|
||||||
locs = wh._search_cycle_count_locations()
|
for rule in self.big_wh.cycle_count_rule_ids:
|
||||||
|
locs += wh._search_cycle_count_locations(rule)
|
||||||
|
locs = locs.exists() # remove duplicated locations.
|
||||||
counts = self.cycle_count_model.search([
|
counts = self.cycle_count_model.search([
|
||||||
('location_id', 'in', locs.ids)])
|
('location_id', 'in', locs.ids)])
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
@@ -225,6 +234,10 @@ class TestStockCycleCount(common.TransactionCase):
|
|||||||
for r in rules:
|
for r in rules:
|
||||||
r._get_rule_description()
|
r._get_rule_description()
|
||||||
self.assertTrue(r.rule_description, 'No description provided')
|
self.assertTrue(r.rule_description, 'No description provided')
|
||||||
|
self.rule_accuracy._get_warehouses()
|
||||||
|
self.assertEqual(self.rule_accuracy.warehouse_ids.ids, self.big_wh.ids,
|
||||||
|
'Rules defined for zones are not getting the right '
|
||||||
|
'warehouse.')
|
||||||
|
|
||||||
def test_user_security(self):
|
def test_user_security(self):
|
||||||
"""Tests user rights."""
|
"""Tests user rights."""
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<tree string="Stock Cycle Count">
|
<tree string="Stock Cycle Count">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="warehouse_ids"/>
|
<field name="warehouse_ids"/>
|
||||||
|
<field name="location_ids"/>
|
||||||
<field name="rule_type"/>
|
<field name="rule_type"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
@@ -47,12 +48,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
<group name="applied_in" string="Applied in:">
|
||||||
|
<p colspan="4">You can apply the cycle count rules in complete
|
||||||
|
warehouses or specific zones. A zone it is
|
||||||
|
understood as a location and all its children.</p>
|
||||||
|
<p colspan="4">In either case you can exclude specific locations
|
||||||
|
going to the locations form and checking the box
|
||||||
|
"Exclude from Cycle Count".</p>
|
||||||
|
<field name="apply_in"/>
|
||||||
|
<field name="warehouse_ids" widget="many2many_tags"/>
|
||||||
|
<field name="location_ids"
|
||||||
|
attrs="{'invisible': [('apply_in', '!=', 'location')]}"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
<notebook>
|
|
||||||
<page string="Applied in">
|
|
||||||
<field name="warehouse_ids"/>
|
|
||||||
</page>
|
|
||||||
</notebook>
|
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -10,15 +10,14 @@
|
|||||||
<field name="inherit_id" ref="stock.view_warehouse"/>
|
<field name="inherit_id" ref="stock.view_warehouse"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<notebook position="before">
|
<notebook position="before">
|
||||||
<group string="Cycle Counting">
|
<group string="Cycle Counting" colspan="4">
|
||||||
<group >
|
<field name="cycle_count_planning_horizon"/>
|
||||||
<field name="cycle_count_planning_horizon"/>
|
<field name="counts_for_accuracy_qty"/>
|
||||||
<field name="counts_for_accuracy_qty"/>
|
<br></br>
|
||||||
</group>
|
<center colspan="4"><h3 colspan="4">Cycle Count Rules
|
||||||
<group>
|
applied in this Warehouse:</h3></center>
|
||||||
<field name="cycle_count_rule_ids" nolabel="1">Cycle
|
<field name="cycle_count_rule_ids" nolabel="1" colspan="4">
|
||||||
count rules</field>
|
Cycle count rules</field>
|
||||||
</group>
|
|
||||||
</group>
|
</group>
|
||||||
</notebook>
|
</notebook>
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
Reference in New Issue
Block a user