[9.0][IMP] stock_cycle_count: add the possibility to define rules for specific zones.

This commit is contained in:
lreficent
2017-05-04 19:21:17 +02:00
committed by Mateu Griful
parent d0295f1266
commit 0ca46546d7
8 changed files with 98 additions and 56 deletions

View File

@@ -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
=========== ===========

View File

@@ -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",

View File

@@ -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

View File

@@ -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':

View File

@@ -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

View File

@@ -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."""

View File

@@ -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>

View File

@@ -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>