[ADD] stock_cycle_count

This commit is contained in:
lreficent
2017-02-17 14:48:08 +01:00
committed by ArnauCForgeFlow
parent 87b598688f
commit 28675d2071
19 changed files with 1003 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
=================
Stock Cycle Count
=================
This module adds the capability to execute a cycle count strategy in a
warehouse through different rules defined by the user.
Installation
============
To install this module, you need to:
* Download this module to your addons path.
* Install the module in your database.
Configuration
=============
You can configure the rules to compute the cycle count, acting as follow:
#. Go to "Inventory > Configuration > Cycle Count Rules"
#. Create as much cycle count rules as you want.
#. Assign the rules to the Warehouse where you want to apply the rules in.
#. Set a "Cycle Count Planning Horizon" for each warehouse.
.. figure:: path/to/local/image.png
:alt: alternative description
:width: 600 px
Usage
=====
Once you have some rules configured for your warehouses, you can proceed as
is described below.
#. Go to "Inventory > Configuration > Warehouse Management > Warehouses".
#. Select all the warehouses you want to compute the rules in.
#. Click on "Action" and then in "Compute Cycle Count Rules".
#. Go to "Inventory Control > Cycle Counts".
#. Select a Cycle Count planned an confirm it, this will create a draft
Inventory Adjustment.
#. In the right top corner of the form view you can access the generated
Inventory Adjustment.
#. Proceed with the Inventory Adjustment as usual.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
: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
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/{project_repo}/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Lois Rilo <lois.rilo@eficent.com>
* Jordi Ballester Alomar <jordi.ballester@eficent.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import models

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Stock Cycle Count",
"summary": "Adds the capability to schedule cycle counts in a "
"warehouse through different rules defined by the user",
"version": "9.0.1.0.0",
"author": "Eficent, "
"Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"category": "Warehouse Management",
"depends": ["stock",
"mail",
"stock_inventory_discrepancy",
"stock_inventory_exclude_sublocation"],
"external_dependencies": {
"python": ['numpy'],
},
"data": [
'views/stock_cycle_count_view.xml',
'views/stock_cycle_count_rule_view.xml',
'views/stock_warehouse_view.xml',
'views/stock_inventory_view.xml',
'views/stock_location_view.xml',
'data/cycle_count_sequence.xml',
'data/cycle_count_ir_cron.xml',
'security/ir.model.access.csv'],
"license": "AGPL-3",
'installable': True,
'application': False,
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data noupdate="1">
<record forcecreate="True"
id="ir_cron_compute_cycle_count_action" model="ir.cron">
<field name="name">Cycle Count Planner Computation</field>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model" eval="'stock.warehouse'"/>
<field name="function" eval="'cron_cycle_count'"/>
<field name="args" eval="'()'" />
</record>
</data>
</openerp>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<data noupdate="1">
<record id="seq_cycle_count" model="ir.sequence">
<field name="name">Cycle Count</field>
<field name="code">stock.cycle.count</field>
<field name="prefix">CC/%(range_year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import stock_cycle_count
from . import stock_cycle_count_rule
from . import stock_location
from . import stock_inventory
from . import stock_warehouse
from . import stock_move

View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
class StockCycleCount(models.Model):
_name = 'stock.cycle.count'
_inherit = 'mail.thread'
@api.one
def _count_inventory_adj(self):
self.inventory_adj_count = len(self.stock_adjustment_ids)
@api.model
def create(self, vals):
vals['name'] = self.env['ir.sequence'].next_by_code(
'stock.cycle.count') or ''
return super(StockCycleCount, self).create(vals)
@api.model
def _company_get(self):
company_id = self.env['res.company']._company_default_get(self._name)
return company_id
name = fields.Char(string='Name', readonly=True)
location_id = fields.Many2one(comodel_name='stock.location',
string='Location',
required=True)
responsible_id = fields.Many2one(comodel_name='res.users',
string='Assigned to')
date_deadline = fields.Date(string='Required Date')
cycle_count_rule_id = fields.Many2one(
comodel_name='stock.cycle.count.rule',
string='Cycle count rule',
required=True)
state = fields.Selection(selection=[
('draft', 'Planned'),
('open', 'Execution'),
('cancelled', 'Cancelled'),
('done', 'Done')
], string='State', default='draft')
stock_adjustment_ids = fields.One2many(comodel_name='stock.inventory',
inverse_name='cycle_count_id',
string='Inventory Adjustment')
inventory_adj_count = fields.Integer(compute=_count_inventory_adj)
company_id = fields.Many2one(comodel_name='res.company',
string='Company',
required=True,
default=_company_get)
@api.one
def do_cancel(self):
self.state = 'cancelled'
@api.model
def _prepare_inventory_adjustment(self):
return {
'name': 'INV/{}'.format(self.name),
'cycle_count_id': self.id,
'location_id': self.location_id.id,
'exclude_sublocation': True
}
@api.one
def action_create_inventory_adjustment(self):
data = self._prepare_inventory_adjustment()
self.env['stock.inventory'].create(data)
self.state = 'open'
return True
@api.multi
def action_view_inventory(self):
action = self.env.ref('stock.action_inventory_form')
result = action.read()[0]
result['context'] = {}
adjustment_ids = sum([cycle_count.stock_adjustment_ids.ids
for cycle_count in self], [])
if len(adjustment_ids) > 1:
result['domain'] = \
"[('id','in',[" + ','.join(map(str, adjustment_ids)) + "])]"
elif len(adjustment_ids) == 1:
res = self.env.ref('stock.view_inventory_form', False)
result['views'] = [(res and res.id or False, 'form')]
result['res_id'] = adjustment_ids and adjustment_ids[0] or False
return result

View File

@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models, _
from openerp.exceptions import UserError
from datetime import timedelta, datetime
class StockCycleCountRule(models.Model):
_name = 'stock.cycle.count.rule'
@api.one
def _compute_currency(self):
self.currency_id = self.env.user.company_id.currency_id
@api.model
def _selection_rule_types(self):
return [
('periodic', _('Periodic')),
('turnover', _('Value Turnover')),
('accuracy', _('Minimum Accuracy')),
('zero', _('Zero Confirmation'))]
@api.one
@api.constrains('rule_type', 'warehouse_ids')
def _check_zero_rule(self):
if self.rule_type == 'zero' and len(self.warehouse_ids) > 1:
raise UserError(
_('Zero confirmation rules can only have one warehouse '
'assigned.')
)
if self.rule_type == 'zero':
zero_rule = self.search([
('rule_type', '=', 'zero'),
('warehouse_ids', '=', self.warehouse_ids.id)])
if len(zero_rule) > 1:
raise UserError(
_('You can only have one zero confirmation rule per '
'warehouse.')
)
@api.onchange('rule_type')
def _get_rule_description(self):
if self.rule_type == 'periodic':
self.rule_description = _('Ensures that at least a defined number '
'of counts in a given period will '
'be run.')
elif self.rule_type == 'turnover':
self.rule_description = _('Schedules a count every time the total '
'turnover of a location exceeds the '
'threshold. This considers every '
'product going into/out of the location')
elif self.rule_type == 'accuracy':
self.rule_description = _('Schedules a count every time the '
'accuracy of a location goes under a '
'given threshold.')
elif self.rule_type == 'zero':
self.rule_description = _('Perform an Inventory Adjustment every '
'time a location in the warehouse runs '
'out of stock in order to confirm it is '
'truly empty.')
else:
self.rule_description = _('(No description provided.)')
@api.constrains('periodic_qty_per_period', 'periodic_count_period')
def _check_negative_periodic(self):
if self.periodic_qty_per_period < 1:
raise UserError(
_('You cannot define a negative or null number of counts per '
'period.')
)
if self.periodic_count_period < 0:
raise UserError(
_('You cannot define a negative period.')
)
name = fields.Char('Name', required=True)
rule_type = fields.Selection(selection="_selection_rule_types",
string='Type of rule',
required=True)
rule_description = fields.Char(string='Rule Description',
compute=_get_rule_description)
active = fields.Boolean(string='Active', default=True)
periodic_qty_per_period = fields.Integer(string='Counts per period',
default=1)
periodic_count_period = fields.Integer(string='Period in days')
turnover_inventory_value_threshold = fields.Float(
string='Turnover Inventory Value Threshold')
currency_id = fields.Many2one(comodel_name='res.currency',
string='Currency',
compute=_compute_currency)
accuracy_threshold = fields.Float(string='Minimum Accuracy Threshold',
digits=(3, 2))
warehouse_ids = fields.Many2many(comodel_name='stock.warehouse',
relation='warehouse_cycle_count_rule_rel',
column1='rule_id',
column2='warehouse_id',
string='Applied in')
def compute_rule(self, locs):
if self.rule_type == 'periodic':
proposed_cycle_counts = self._compute_rule_periodic(locs)
elif self.rule_type == 'turnover':
proposed_cycle_counts = self._compute_rule_turnover(locs)
elif self.rule_type == 'accuracy':
proposed_cycle_counts = self._compute_rule_accuracy(locs)
return proposed_cycle_counts
@api.model
def _propose_cycle_count(self, date, location):
cycle_count = {
'date': date.strftime('%Y-%m-%d %H:%M:%S'),
'location': location,
'rule_type': self
}
return cycle_count
@api.model
def _compute_rule_periodic(self, locs):
cycle_counts = []
for loc in locs:
last_inventories = self.env['stock.inventory'].search([
('location_id', '=', loc.id),
('state', 'in', ['confirm', 'done', 'draft'])]).mapped('date')
if last_inventories:
latest_inventory = sorted(last_inventories, reverse=True)[0]
try:
period = self.periodic_count_period / \
self.periodic_qty_per_period
next_date = datetime.strptime(
latest_inventory, '%Y-%m-%d %H:%M:%S') + timedelta(
days=period)
except Exception as e:
raise UserError(
_('Error found determining the frequency of periodic '
'cycle count rule. %s') % e.message)
else:
next_date = datetime.today()
cycle_count = self._propose_cycle_count(next_date, loc)
cycle_counts.append(cycle_count)
return cycle_counts
@api.model
def _get_turnover_moves(self, location, date):
moves = self.env['stock.move'].search([
'|', ('location_id', '=', location.id),
('location_dest_id', '=', location.id),
('date', '>', date),
('state', '=', 'done')])
return moves
@api.model
def _compute_turnover(self, move):
turnover = move.product_uom_qty * move.product_id.standard_price
return turnover
@api.model
def _compute_rule_turnover(self, locs):
cycle_counts = []
for loc in locs:
last_inventories = self.env['stock.inventory'].search([
('location_id', '=', loc.id),
('state', 'in', ['confirm', 'done', 'draft'])]).mapped('date')
if last_inventories:
latest_inventory = sorted(last_inventories, reverse=True)[0]
moves = self._get_turnover_moves(loc, latest_inventory)
if moves:
total_turnover = 0.0
for m in moves:
turnover = self._compute_turnover(m)
total_turnover += turnover
try:
if total_turnover > \
self.turnover_inventory_value_threshold:
next_date = datetime.today()
cycle_count = self._propose_cycle_count(next_date,
loc)
cycle_counts.append(cycle_count)
except Exception as e:
raise UserError(_(
'Error found when comparing turnover with the '
'rule threshold. %s') % e.message)
else:
next_date = datetime.today()
cycle_count = self._propose_cycle_count(next_date, loc)
cycle_counts.append(cycle_count)
return cycle_counts
@api.model
def _compute_rule_accuracy(self, locs):
cycle_counts = []
for loc in locs:
if loc.loc_accuracy < self.accuracy_threshold:
next_date = datetime.today()
cycle_count = self._propose_cycle_count(next_date, loc)
cycle_counts.append(cycle_count)
return cycle_counts

View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
class StockInventory(models.Model):
_inherit = 'stock.inventory'
@api.one
def _compute_inventory_accuracy(self):
total_qty = sum(self.line_ids.mapped('theoretical_qty'))
abs_discrepancy = sum(self.line_ids.mapped(
lambda x: abs(x.discrepancy_qty)))
if total_qty:
self.inventory_accuracy = 100 * (total_qty - abs_discrepancy) / \
total_qty
if not self.line_ids and self.state == 'done':
self.inventory_accuracy = 100.0
cycle_count_id = fields.Many2one(comodel_name='stock.cycle.count',
string='Stock Cycle Count',
ondelete='cascade')
inventory_accuracy = fields.Float(string='Accuracy',
compute=_compute_inventory_accuracy,
digits=(3, 2))
@api.multi
def action_done(self):
if self.cycle_count_id:
self.cycle_count_id.state = 'done'
return super(StockInventory, self).action_done()

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import logging
from openerp import api, fields, models, tools
from datetime import datetime
_logger = logging.getLogger(__name__)
try:
from numpy import mean
NUMPY_PATH = tools.find_in_path('numpy')
except (ImportError, IOError) as err:
_logger.debug(err)
class StockLocation(models.Model):
_inherit = 'stock.location'
@api.one
def _compute_loc_accuracy(self):
history = self.env['stock.inventory'].search([
('location_id', '=', self.id), ('state', '=', 'done')])
history = history.sorted(key=lambda r: r.write_date, reverse=True)
if history:
wh_id = self.get_warehouse(self)
wh = self.env['stock.warehouse'].browse(wh_id)
if len(history) > wh.counts_for_accuracy_qty:
self.loc_accuracy = mean(history[:wh.counts_for_accuracy_qty].
mapped('inventory_accuracy'))
else:
self.loc_accuracy = mean(history.mapped('inventory_accuracy'))
zero_confirmation_disabled = fields.Boolean(
string='Disable Zero Confirmations',
default=False,
help='Define whether this location will trigger a zero-confirmation '
'validation when a rule for its warehouse is defined to perform '
'zero-confirmations.')
cycle_count_disabled = fields.Boolean(
string='Exclude from Cycle Count',
default=False,
help='Define whether the location is going to be cycle counted.')
qty_variance_inventory_threshold = fields.Float('Acceptable Inventory '
'Quantity Variance '
'Threshold')
loc_accuracy = fields.Float(string='Inventory Accuracy',
compute=_compute_loc_accuracy,
digits=(3, 2))
@api.model
def _get_zero_confirmation_domain(self):
domain = [('location_id', '=', self.id)]
return domain
@api.one
def check_zero_confirmation(self):
if not self.zero_confirmation_disabled:
wh_id = self.get_warehouse(self)
wh = self.env['stock.warehouse'].browse(wh_id)
rule_model = self.env['stock.cycle.count.rule']
zero_rule = rule_model.search([
('rule_type', '=', 'zero'),
('warehouse_ids', '=', wh.id)])
if zero_rule:
quants = self.env['stock.quant'].search(
self._get_zero_confirmation_domain())
if not quants:
self.create_zero_confirmation_cycle_count()
def create_zero_confirmation_cycle_count(self):
date = datetime.today().strftime('%Y-%m-%d %H:%M:%S')
wh_id = self.get_warehouse(self)
date_horizon = self.env['stock.warehouse'].browse(
wh_id).get_horizon_date()[0].strftime('%Y-%m-%d %H:%M:%S')
counts_planned = self.env['stock.cycle.count'].search([
('date_deadline', '<', date_horizon), ('state', '=', 'draft'),
('location_id', '=', self.id)])
if counts_planned:
counts_planned.write({'state': 'cancelled'})
rule = self.env['stock.cycle.count.rule'].search([
('rule_type', '=', 'zero'), ('warehouse_ids', '=', wh_id)])
self.env['stock.cycle.count'].create({
'date_deadline': date,
'location_id': self.id,
'cycle_count_rule_id': rule.id,
'state': 'draft'
})
return True

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, models
class StockMove(models.Model):
_inherit = 'stock.move'
@api.one
def action_done(self):
super(StockMove, self).action_done()
self.location_id.check_zero_confirmation()
return True

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
from datetime import datetime, timedelta
class StockWarehouse(models.Model):
_inherit = 'stock.warehouse'
cycle_count_rule_ids = fields.Many2many(
comodel_name='stock.cycle.count.rule',
relation='warehouse_cycle_count_'
'rule_rel',
column1='warehouse_id',
column2='rule_id',
string='Cycle Count Rules')
cycle_count_planning_horizon = fields.Integer(
string='Cycle Count Planning Horizon (in days)',
help='Cycle Count planning horizon in days. Only the counts inside '
'the horizon will be created.')
counts_for_accuracy_qty = fields.Integer(
string='Inventories for location accuracy calculation',
default=1,
help='Number of latest inventories used to calculate location '
'accuracy')
@api.one
def get_horizon_date(self):
date = datetime.today()
delta = timedelta(self.cycle_count_planning_horizon)
date_horizon = date + delta
return date_horizon
@api.model
def _get_cycle_count_locations_search_domain(self):
wh_parent_left = self.view_location_id.parent_left
wh_parent_right = self.view_location_id.parent_right
domain = [('parent_left', '>', wh_parent_left),
('parent_right', '<', wh_parent_right),
('cycle_count_disabled', '=', False)]
return domain
@api.model
def _search_cycle_count_locations(self):
locations = self.env['stock.location'].search(
self._get_cycle_count_locations_search_domain())
return locations
@api.model
def _cycle_count_rules_to_compute(self):
rules = self.cycle_count_rule_ids.search([
('rule_type', '!=', 'zero')])
return rules
@api.one
def action_compute_cycle_count_rules(self):
''' Apply the rule in all the sublocations of a given warehouse(s) and
returns a list with required dates for the cycle count of each
location '''
proposed_cycle_counts = []
locations = self._search_cycle_count_locations()
rules = self._cycle_count_rules_to_compute()
if locations:
for rule in rules:
proposed_cycle_counts.extend(rule.compute_rule(locations))
if proposed_cycle_counts:
locations = list(set([d['location'] for d in
proposed_cycle_counts]))
for loc in locations:
proposed_for_loc = filter(lambda x: x['location'] == loc,
proposed_cycle_counts)
earliest_date = min([d['date'] for d in proposed_for_loc])
cycle_count_proposed = filter(lambda x: x['date'] ==
earliest_date,
proposed_for_loc)[0]
domain = [('location_id', '=', loc.id),
('state', 'in', ['draft'])]
existing_cycle_counts = self.env['stock.cycle.count'].search(
domain)
if existing_cycle_counts and cycle_count_proposed['date'] <\
existing_cycle_counts.date_deadline:
self.env['stock.cycle.count'].create({
'date_deadline': cycle_count_proposed['date'],
'location_id': cycle_count_proposed['location'].id,
'cycle_count_rule_id': cycle_count_proposed[
'rule_type'].id,
'state': 'draft'
})
existing_cycle_counts.state = 'cancelled'
delta = datetime.strptime(
cycle_count_proposed['date'],
'%Y-%m-%d %H:%M:%S') - datetime.today()
if not existing_cycle_counts and \
delta.days < self.cycle_count_planning_horizon:
self.env['stock.cycle.count'].create({
'date_deadline': cycle_count_proposed['date'],
'location_id': cycle_count_proposed['location'].id,
'cycle_count_rule_id': cycle_count_proposed[
'rule_type'].id,
'state': 'draft'
})
@api.model
def cron_cycle_count(self):
whs = self.search([])
whs.action_compute_cycle_count_rules()
return True

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_cycle_count_user,stock.cycle.count user,model_stock_cycle_count,stock.group_stock_user,1,1,1,0
access_stock_cycle_count_manager,stock.cycle.count manager,model_stock_cycle_count,stock.group_stock_manager,1,1,1,1
access_stock_cycle_count_rule_user,stock.cycle.count.rule user,model_stock_cycle_count_rule,stock.group_stock_user,1,0,0,0
access_stock_cycle_count_rule_manager,stock.cycle.count.rule manager,model_stock_cycle_count_rule,stock.group_stock_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_stock_cycle_count_user stock.cycle.count user model_stock_cycle_count stock.group_stock_user 1 1 1 0
3 access_stock_cycle_count_manager stock.cycle.count manager model_stock_cycle_count stock.group_stock_manager 1 1 1 1
4 access_stock_cycle_count_rule_user stock.cycle.count.rule user model_stock_cycle_count_rule stock.group_stock_user 1 0 0 0
5 access_stock_cycle_count_rule_manager stock.cycle.count.rule manager model_stock_cycle_count_rule stock.group_stock_manager 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- Stock Cycle Count Rule List view -->
<record id="stock_cycle_count_rule_tree_view" model="ir.ui.view">
<field name="name">stock.cycle.count.rule.tree</field>
<field name="model">stock.cycle.count.rule</field>
<field name="arch" type="xml">
<tree string="Stock Cycle Count">
<field name="name"/>
<field name="warehouse_ids"/>
<field name="rule_type"/>
</tree>
</field>
</record>
<!-- Stock Cycle Count Rule Form view -->
<record id="stock_cycle_count_rule_form_view" model="ir.ui.view">
<field name="name">stock.cycle.count.rule.form</field>
<field name="model">stock.cycle.count.rule</field>
<field name="arch" type="xml">
<form string="cycle counts test:">
<sheet>
<group name="top">
<group>
<field name="name"/>
<field name="rule_type"/>
<field name="rule_description"/>
<field name="active"/>
</group>
<group name="specific rule fields">
<field name="periodic_qty_per_period"
attrs="{'invisible': [('rule_type', '!=', 'periodic')]}"/>
<field name="periodic_count_period"
attrs="{'invisible': [('rule_type', '!=', 'periodic')]}"/>
<field name="turnover_inventory_value_threshold"
attrs="{'invisible': [('rule_type', '!=', 'turnover')]}"
widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="currency_id"
invisible="True"/>
<label for="accuracy_threshold"
attrs="{'invisible': [('rule_type', '!=', 'accuracy')]}"/>
<div attrs="{'invisible': [('rule_type', '!=', 'accuracy')]}">
<field name="accuracy_threshold" class="oe_inline"/> %
</div>
</group>
</group>
<notebook>
<page string="Applied in">
<field name="warehouse_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Action to open Stock Cycle Count Rule -->
<act_window id="action_stock_cycle_count_rules"
name="Stock Cycle Count Rules"
res_model="stock.cycle.count.rule"
view_mode="tree,form" />
<menuitem id="menu_stock_cycle_count_rule"
name="Cycle Count Rules"
parent="stock.menu_stock_config_settings"
action="action_stock_cycle_count_rules" />
</odoo>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- Stock Cycle Count List view -->
<record id="stock_cycle_count_tree_view" model="ir.ui.view">
<field name="name">stock.cycle.count.tree</field>
<field name="model">stock.cycle.count</field>
<field name="arch" type="xml">
<tree string="Stock Cycle Count">
<field name="name"/>
<field name="location_id"/>
<field name="cycle_count_rule_id"/>
<field name="responsible_id"/>
<field name="date_deadline"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="stock_cycle_count_form_view" model="ir.ui.view">
<field name="name">stock.cycle.count.form</field>
<field name="model">stock.cycle.count</field>
<field name="arch" type="xml">
<form string="cycle counts test:">
<header>
<button name="action_create_inventory_adjustment"
type="object" states="draft"
string="Confirm" class="oe_highlight" />
<button name="do_cancel" type="object"
string="Cancel" states="draft"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,open,done"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box"
attrs="{'invisible':
[('state', 'not in', ('draft', 'open', 'done'))]}">
<button name="action_view_inventory"
type="object" class="oe_stat_button"
icon="fa-building-o">
<field name="inventory_adj_count"
widget="statinfo"
help="Inventory adjustments associated"
modifiers="{'readonly': true}"
string="Inventory Adjustments"/>
</button>
</div>
<div class="oe_title">
<label string="Cycle Count"/>
<h1>
<field name="name"/>
</h1>
</div>
<group name="top">
<group name="left">
<field name="location_id">Location</field>
<field name="cycle_count_rule_id"/>
</group>
<group name="right">
<field name="responsible_id">Assigned to</field>
<field name="date_deadline">Deadline Date</field>
<field name="company_id"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<!-- Action to open Stock Cycle Count -->
<act_window id="action_stock_cycle_count"
name="Stock Cycle Count"
res_model="stock.cycle.count"
view_mode="tree,form" />
<menuitem id="menu_stock_cycle_count"
name="Cycle Counts" parent="stock.menu_stock_inventory_control"
action="action_stock_cycle_count" />
</odoo>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_inventory_tree" model="ir.ui.view">
<field name="name">Inventory tree view - cycle count extension</field>
<field name="model">stock.inventory</field>
<field name="inherit_id" ref="stock.view_inventory_tree"/>
<field name="arch" type="xml">
<field name="date" position="after">
<field name="cycle_count_id"/>
<field name="inventory_accuracy"/>
</field>
</field>
</record>
<record id="view_inventory_form" model="ir.ui.view">
<field name="name">Inventory form view - cycle count extension </field>
<field name="model">stock.inventory</field>
<field name="inherit_id" ref="stock.view_inventory_form"/>
<field name="arch" type="xml">
<field name="date" position="after">
<field name="cycle_count_id"/>
<label for="inventory_accuracy"/>
<div>
<field name="inventory_accuracy" class="oe_inline"/> %
</div>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.actions.act_window" id="act_accuracy_stats">
<field name="domain">[('location_id', '=', active_ids),
('state', '=', 'done')]</field>
<field name="name">Accuracy Stats</field>
<field name="res_model">stock.inventory</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record id="view_location_form" model="ir.ui.view">
<field name="name">Location form - cycle count extension</field>
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock.view_location_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button string="Accuracy Stats" class="oe_stat_button"
icon="fa-line-chart" name="%(act_accuracy_stats)d"
type="action"/>
</xpath>
<field name="active" position="after">
<field name="zero_confirmation_disabled"/>
<field name="cycle_count_disabled"/>
<label for="loc_accuracy"/>
<div>
<field name="loc_accuracy" class="oe_inline"/> %
</div>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_warehouse_form" model="ir.ui.view">
<field name="name">Warehouse form - cycle count extension</field>
<field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse"/>
<field name="arch" type="xml">
<notebook position="before">
<group string="Cycle Counting">
<group >
<field name="cycle_count_planning_horizon"/>
<field name="counts_for_accuracy_qty"/>
</group>
<group>
<field name="cycle_count_rule_ids" nolabel="1">Cycle
count rules</field>
</group>
</group>
</notebook>
</field>
</record>
<record id="action_server_warehouse_execute_cycle_count"
model="ir.actions.server">
<field name="name">Compute Cycle Count Rules</field>
<field name="condition">True</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_stock_warehouse" />
<field name="state">code</field>
<field name="code">self.action_compute_cycle_count_rules(cr, uid, context.get('active_ids', []), context=context)</field>
</record>
<record model="ir.values" id="action_warehouse_execute_cycle_count">
<field name="name">action_warehouse_execute_cycle_count</field>
<field name="action_id"
ref="action_server_warehouse_execute_cycle_count" />
<field name="value" eval="'ir.actions.server,' + str(ref('action_server_warehouse_execute_cycle_count'))" />
<field name="key">action</field>
<field name="model_id" ref="model_stock_warehouse" />
<field name="model">stock.warehouse</field>
<field name="key2">client_action_multi</field>
</record>
</odoo>