[ADD] new module 'stock_demand_estimate'

This commit is contained in:
Jordi Ballester
2017-05-30 14:39:21 +02:00
committed by Lois Rilo
parent fe20ebe435
commit 0f9f956132
14 changed files with 657 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
.. 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 Demand Estimate
=====================
This module allows to create demand estimates for a given product and
location, on configurable time periods.
The module does not provide in itself any specific usage of the estimates.
Installation
============
This module relies on the OCA module '2D matrix for x2many fields', and can
be downloaded from:
* Github: https://github.com/OCA/web/tree/8.0/web_widget_x2many_2d_matrix
Usage
=====
Go to 'Warehouse / Configuration / Demand Estimate Periods' and define your
estimating periods (monthly or weekly).
Go to 'Warehouse / Demand Planning / Create Demand Estimates' to create or
update your demand estimates.
Go to 'Warehouse / Demand Planning / Demand Estimates' to review the
estimates created.
.. 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/8.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/stock-logistics-warehouse/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* 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,7 @@
# -*- 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
from . import wizards

View File

@@ -0,0 +1,25 @@
# -*- 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).
{
"name": "Stock Demand Estimate",
"summary": "Allows to create demand estimates.",
"version": "8.0.1.0.0",
"author": "Eficent Business and IT Consulting Services S.L,"
"Odoo Community Association (OCA)",
"website": "https://www.odoo-community.org",
"category": "Warehouse Management",
"depends": ["stock",
"web_widget_x2many_2d_matrix"
],
"data": ["security/ir.model.access.csv",
"security/stock_security.xml",
"views/stock_demand_estimate_period_view.xml",
"views/stock_demand_estimate_view.xml",
"wizards/stock_demand_estimate_wizard_view.xml",
],
"license": "AGPL-3",
'installable': True,
'application': True,
}

View File

@@ -0,0 +1,7 @@
# -*- 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 stock_demand_estimate_period
from . import stock_demand_estimate

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# © 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# © 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models, _
import openerp.addons.decimal_precision as dp
from openerp.exceptions import Warning as UserError
class StockDemandEstimate(models.Model):
_name = 'stock.demand.estimate'
_description = 'Stock Demand Estimate Line'
@api.multi
@api.depends('product_id', 'product_uom', 'product_uom_qty')
def _compute_product_qty(self):
for rec in self:
if rec.product_uom:
rec.product_qty = rec.product_uom._compute_qty(
rec.product_id.uom_id.id, rec.product_uom_qty,
rec.product_uom.id)
def _set_product_qty(self):
raise UserError(_('The requested operation cannot be '
'processed because of a programming error '
'setting the `product_qty` field instead '
'of the `product_uom_qty`.'))
@api.multi
def _compute_daily_qty(self):
for rec in self:
rec.daily_qty = rec.product_qty / rec.period_id.days
period_id = fields.Many2one(
comodel_name="stock.demand.estimate.period",
string="Estimating Period",
required=True)
product_id = fields.Many2one(comodel_name="product.product",
string="Product", required=True)
product_uom = fields.Many2one(comodel_name="product.uom",
string="Unit of measure")
location_id = fields.Many2one(comodel_name="stock.location",
string="Location", required=True)
product_uom_qty = fields.Float(
string="Quantity",
digits_compute=dp.get_precision('Product Unit of Measure'))
product_qty = fields.Float('Real Quantity', compute='_compute_product_qty',
inverse='_set_product_qty', digits=0,
store=True,
help='Quantity in the default UoM of the '
'product', readonly=True)
daily_qty = fields.Float(string='Quantity / Day',
compute='_compute_daily_qty')
company_id = fields.Many2one(
comodel_name='res.company', string='Company', required=True,
default=lambda self: self.env['res.company']._company_default_get(
'stock.demand.estimate'))
@api.multi
def name_get(self):
res = []
for rec in self:
name = "%s - %s - %s" % (rec.period_id.name, rec.product_id.name,
rec.location_id.name)
res.append((rec.id, name))
return res
@api.model
def get_quantity_by_date_range(self, date_from, date_to):
# Check if the dates overlap with the period
period_date_from = fields.Date.from_string(self.period_id.date_from)
period_date_to = fields.Date.from_string(self.period_id.date_to)
if date_from <= period_date_to and period_date_from <= date_to:
overlap_date_from = max(period_date_from, date_from)
overlap_date_to = min(period_date_to, date_to)
days = (abs(overlap_date_to-overlap_date_from)).days + 1
return days * self.daily_qty
return 0.0

View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# © 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# © 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models, _
from openerp.exceptions import Warning as UserError
class StockDemandEstimatePeriod(models.Model):
_name = 'stock.demand.estimate.period'
_description = 'Stock Demand Estimate Period'
_order = 'date_from'
@api.multi
@api.depends('date_from', 'date_to')
def _compute_days(self):
for rec in self:
if rec.date_from and rec.date_to:
rec.days = (fields.Date.from_string(rec.date_to) -
fields.Date.from_string(rec.date_from)).days + 1
name = fields.Char(string="Name", required=True)
date_from = fields.Date(string="Date From", required=True)
date_to = fields.Date(string="Date To", required=True)
days = fields.Float(string="Days between dates",
compute='_compute_days', store=True, readonly=True)
estimate_ids = fields.One2many(
comodel_name="stock.demand.estimate",
inverse_name="period_id")
company_id = fields.Many2one(
comodel_name='res.company', string='Company', required=True,
default=lambda self: self.env['res.company']._company_default_get(
'stock.demand.estimate.period'))
@api.multi
@api.constrains('name', 'date_from', 'date_to')
def _check_period(self):
for period in self:
self.env.cr.execute('SELECT id, date_from, date_to \
FROM stock_demand_estimate_period \
WHERE (date_from <= %s and %s <= date_to) \
AND id <> %s', (period.date_to, period.date_from, period.id))
res = self.env.cr.fetchall()
if res:
raise UserError(_('Two periods cannot overlap.'))

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_demand_estimate,stock.orderpoint.demand.estimate,model_stock_demand_estimate,stock.group_stock_user,1,0,0,0
access_stock_demand_estimate_system,stock.orderpoint.demand.estimate system,model_stock_demand_estimate,stock.group_stock_manager,1,1,1,1
access_stock_demand_estimate_period,stock.orderpoint.demand.estimate.period,model_stock_demand_estimate_period,stock.group_stock_user,1,0,0,0
access_stock_demand_estimate_period_system,stock.orderpoint.demand.estimate.period system,model_stock_demand_estimate_period,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_demand_estimate stock.orderpoint.demand.estimate model_stock_demand_estimate stock.group_stock_user 1 0 0 0
3 access_stock_demand_estimate_system stock.orderpoint.demand.estimate system model_stock_demand_estimate stock.group_stock_manager 1 1 1 1
4 access_stock_demand_estimate_period stock.orderpoint.demand.estimate.period model_stock_demand_estimate_period stock.group_stock_user 1 0 0 0
5 access_stock_demand_estimate_period_system stock.orderpoint.demand.estimate.period system model_stock_demand_estimate_period stock.group_stock_manager 1 1 1 1

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record model="ir.rule" id="stock_demand_estimate_comp_rule">
<field name="name">Stock demand estimate multi-company</field>
<field name="model_id" ref="model_stock_demand_estimate"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="stock_demand_estimate_period_comp_rule">
<field name="name">Stock demand estimate multi-company</field>
<field name="model_id"
ref="model_stock_demand_estimate_period"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</openerp>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,49 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view"
id="view_stock_demand_estimate_period_tree">
<field name="name">stock.demand.estimate.period.tree</field>
<field name="model">stock.demand.estimate.period</field>
<field name="arch" type="xml">
<tree string="Stock Demand Estimate Period" editable="top">
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="days"/>
</tree>
</field>
</record>
<record id="view_stock_demand_estimate_period_search"
model="ir.ui.view">
<field name="name">stock.demand.estimate.period.search</field>
<field name="model">stock.demand.estimate.period</field>
<field name="arch" type="xml">
<search string="Search Stock Demand Estimates">
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
</search>
</field>
</record>
<record model="ir.actions.act_window"
id="stock_demand_estimate_period_form_action">
<field name="name">Stock Demand Estimate Periods</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.demand.estimate.period</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id"
ref="view_stock_demand_estimate_period_search"/>
</record>
<menuitem
id="menu_stock_demand_estimate_period"
parent="stock.menu_stock_config_settings"
action="stock_demand_estimate_period_form_action"/>
</data>
</openerp>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view"
id="view_stock_demand_estimate_tree">
<field name="name">stock.demand.estimate.tree</field>
<field name="model">stock.demand.estimate</field>
<field name="arch" type="xml">
<tree string="Stock Demand Estimate" editable="top">
<field name="period_id"/>
<field name="product_id"/>
<field name="location_id"/>
<field name="product_uom_qty"/>
<field name="product_uom"/>
<field name="product_qty"/>
<field name="daily_qty"/>
</tree>
</field>
</record>
<record id="view_stock_demand_estimate_search"
model="ir.ui.view">
<field name="name">stock.demand.estimate.search</field>
<field name="model">stock.demand.estimate</field>
<field name="arch" type="xml">
<search string="Search Stock Demand Estimates">
<field name="period_id"/>
<field name="product_id"/>
<field name="location_id"/>
</search>
</field>
</record>
<record model="ir.actions.act_window"
id="stock_demand_estimate_form_action">
<field name="name">Stock Demand Estimates</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.demand.estimate</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id"
ref="view_stock_demand_estimate_search"/>
</record>
<menuitem id="menu_stock_demand_planning" name="Demand Planning"
parent="stock.menu_stock_root" sequence="10"/>
<menuitem
id="menu_stock_demand_estimate"
parent="menu_stock_demand_planning"
action="stock_demand_estimate_form_action"/>
</data>
</openerp>

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 stock_demand_estimate_wizard

View File

@@ -0,0 +1,189 @@
# -*- coding: utf-8 -*-
# © 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# © 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models, _
import openerp.addons.decimal_precision as dp
from openerp.exceptions import Warning as UserError
_PERIOD_SELECTION = [
('monthly', 'Monthly'),
('weekly', 'Weekly')
]
class StockDemandEstimateSheet(models.TransientModel):
_name = 'stock.demand.estimate.sheet'
_description = 'Stock Demand Estimate Sheet'
def _default_date_from(self):
return self.env.context.get('date_from', False)
def _default_date_to(self):
return self.env.context.get('date_to', False)
def _default_location_id(self):
location_id = self.env.context.get('location_id', False)
if location_id:
return self.env['stock.location'].browse(location_id)
else:
return False
def _default_estimate_ids(self):
date_from = self.env.context.get('date_from', False)
date_to = self.env.context.get('date_to', False)
location_id = self.env.context.get('location_id', False)
product_ids = self.env.context.get('product_ids', False)
domain = [('date_from', '>=', date_from),
('date_to', '<=', date_to)]
periods = self.env['stock.demand.estimate.period'].search(
domain)
products = self.env['product.product'].browse(product_ids)
lines = []
for product in products:
name_y = ''
if product.default_code:
name_y += '[%s] ' % product.default_code
name_y += product.name
name_y += ' - %s' % product.uom_id.name
for period in periods:
estimates = self.env['stock.demand.estimate'].search(
[('product_id', '=', product.id),
('period_id', '=', period.id),
('location_id', '=', location_id)])
if estimates:
lines.append((0, 0, {
'value_x': period.name,
'value_y': name_y,
'period_id': period.id,
'product_id': product.id,
'product_uom': estimates[0].product_uom.id,
'location_id': location_id,
'estimate_id': estimates[0].id,
'product_uom_qty': estimates[0].product_uom_qty
}))
else:
lines.append((0, 0, {
'value_x': period.name,
'value_y': name_y,
'period_id': period.id,
'product_id': product.id,
'product_uom': product.uom_id.id,
'location_id': location_id,
'product_uom_qty': 0.0
}))
return lines
date_from = fields.Date(string="Date From", readonly=True,
default=_default_date_from)
date_to = fields.Date(string="Date From", readonly=True,
default=_default_date_to)
location_id = fields.Many2one(comodel_name="stock.location",
string="Location", readonly=True,
default=_default_location_id)
line_ids = fields.Many2many(
string="Estimates",
comodel_name='stock.demand.estimate.sheet.line',
rel='stock_demand_estimate_line_rel',
default=_default_estimate_ids)
@api.model
def _prepare_estimate_data(self, line):
return {
'period_id': line.period_id.id,
'product_id': line.product_id.id,
'location_id': line.location_id.id,
'product_uom_qty': line.product_uom_qty,
'product_uom': line.product_id.uom_id.id,
}
@api.multi
def button_validate(self):
res = []
for line in self.line_ids:
if line.estimate_id:
line.estimate_id.product_uom_qty = line.product_uom_qty
res.append(line.estimate_id.id)
else:
data = self._prepare_estimate_data(line)
estimate = self.env['stock.demand.estimate'].create(
data)
res.append(estimate.id)
res = {
'domain': [('id', 'in', res)],
'name': _('Stock Demand Estimates'),
'src_model': 'stock.demand.estimate.wizard',
'view_type': 'form',
'view_mode': 'tree',
'res_model': 'stock.demand.estimate',
'type': 'ir.actions.act_window'
}
return res
class StockDemandEstimateSheetLine(models.TransientModel):
_name = 'stock.demand.estimate.sheet.line'
_description = 'Stock Demand Estimate Sheet Line'
estimate_id = fields.Many2one(comodel_name='stock.demand.estimate')
period_id = fields.Many2one(
comodel_name='stock.demand.estimate.period',
string='Period')
location_id = fields.Many2one(comodel_name='stock.location',
string="Stock Location")
product_id = fields.Many2one(comodel_name='product.product',
string='Product')
value_x = fields.Char(string='Period')
value_y = fields.Char(string='Product')
product_uom_qty = fields.Float(
string="Quantity", digits_compute=dp.get_precision('Product UoM'))
class DemandEstimateWizard(models.TransientModel):
_name = 'stock.demand.estimate.wizard'
_description = 'Stock Demand Estimate Wizard'
date_from = fields.Date(string="Date From", required=True)
date_to = fields.Date(string="Date To", required=True)
location_id = fields.Many2one(comodel_name="stock.location",
string="Location", required=True)
product_ids = fields.Many2many(
comodel_name="product.product",
string="Products")
@api.multi
def _prepare_demand_estimate_sheet(self):
self.ensure_one()
return {
'date_from': self.date_from,
'date_to': self.date_to,
'location_id': self.location_id.id,
}
@api.multi
def create_sheet(self):
self.ensure_one()
if not self.product_ids:
raise UserError(_('You must select at lease one product.'))
context = {
'date_from': self.date_from,
'date_to': self.date_to,
'location_id': self.location_id.id,
'product_ids': self.product_ids.ids
}
res = {
'context': context,
'name': _('Estimate Sheet'),
'src_model': 'stock.demand.estimate.wizard',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'res_model': 'stock.demand.estimate.sheet',
'type': 'ir.actions.act_window'
}
return res

View File

@@ -0,0 +1,85 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view"
id="view_stock_demand_estimate_sheet_form">
<field name="name">stock.demand.estimate.sheet.form</field>
<field name="model">stock.demand.estimate.sheet</field>
<field name="arch" type="xml">
<form string="Stock Demand Estimate Sheet">
<group>
<group name="dates">
<label for="date_from" string="Timesheet Period"/>
<div><field name="date_from" class="oe_inline"/> to <field name="date_to" class="oe_inline"/></div>
</group>
<div/>
<group name="attributes">
<field name="location_id"/>
</group>
</group>
<div/>
<group string="Estimated quantity">
<field name="line_ids" nolabel="1"
widget="x2many_2d_matrix"
field_x_axis="value_x"
field_y_axis="value_y"
field_value="product_uom_qty"/>
</group>
<footer>
<button name="button_validate"
type="object"
string="Validate"
class="oe_highlight"/>
<button class="oe_link"
special="cancel"
string="Cancel"/>
</footer>
</form>
</field>
</record>
<record model="ir.ui.view"
id="view_demand_estimate_wizard_form">
<field name="name">stock.demand.estimate.wizard.form</field>
<field name="model">stock.demand.estimate.wizard</field>
<field name="arch" type="xml">
<form string="Stock Demand Estimate Wizard">
<group>
<group name="dates">
<label for="date_from" string="Period"/>
<div><field name="date_from" class="oe_inline"/> to <field name="date_to" class="oe_inline"/></div>
</group>
<div/>
<group name="attributes">
<field name="location_id"/>
</group>
</group>
<div/>
<group name="products" string="Products">
<field name="product_ids" nolabel="1"/>
</group>
<footer>
<button name="create_sheet" string="Prepare"
type="object" class="oe_highlight" /> or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<act_window name="Create Stock Demand Estimates"
res_model="stock.demand.estimate.wizard"
src_model="stock.demand.estimate.sheet"
view_mode="form"
target="new"
key2="client_action_multi"
id="action_stock_demand_estimate_wizard"/>
<menuitem
id="menu_stock_demand_estimate_wizard"
parent="menu_stock_demand_planning"
action="action_stock_demand_estimate_wizard"/>
</data>
</openerp>