Skip to content

Commit

Permalink
Migrate stock_available_sale to v8
Browse files Browse the repository at this point in the history
Correctly segregates templates and variants.
Rewritten in new API.
Uses the ORM instead of SQL calls.
Conforms better to OCA standards
'Quoted qty' is now positive. Explain UoM feature drop.
Added directions to block quotations using sale_exceptions
Rewrote the tests in python and added new tests regarding warehouses, locations, variants and templates ; and now test the quantity available for sale.
  • Loading branch information
Lionel Sausin committed Nov 6, 2015
1 parent 1fe140c commit e4daf3d
Show file tree
Hide file tree
Showing 13 changed files with 478 additions and 318 deletions.
14 changes: 7 additions & 7 deletions stock_available/models/res_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ class StockConfig(models.TransientModel):
"available to promise.\n"
"This installs the module stock_available_immediately.")

# module_stock_available_sale = fields.Boolean(
# string='Exclude goods already in sale quotations',
# help="This will subtract quantities from the sale quotations from "
# "the quantities available to promise.\n"
# "This installs the modules stock_available_sale.\n"
# "If the modules sale and sale_delivery_date are not "
# "installed, this will install them too")
module_stock_available_sale = fields.Boolean(
string='Exclude goods already in sale quotations',
help="This will subtract quantities from the sale quotations from "
"the quantities available to promise.\n"
"This installs the modules stock_available_sale.\n"
"If the modules sale and sale_delivery_date are not "
"installed, this will install them too")

# module_stock_available_mrp = fields.Boolean(
# string='Include the production potential',
Expand Down
4 changes: 2 additions & 2 deletions stock_available/views/res_config_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
<field name="module_stock_available_immediately" class="oe_inline" />
<label for="module_stock_available_immediately" />
</div>
<!-- <div>
<div>
<field name="module_stock_available_sale" class="oe_inline" />
<label for="module_stock_available_sale" />
</div> -->
</div>
<!-- <div>
<field name="module_stock_available_mrp" class="oe_inline" />
<label for="module_stock_available_mrp" />
Expand Down
24 changes: 21 additions & 3 deletions stock_available_sale/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,31 @@ the quantity available to promise.
"Quoted" is defined as the sum of the quantities of this Product in
Sale Quotations, taking the context's shop or warehouse into account.

Warning for salespersons
------------------------
If you want to keep salespersons from concluding sales that you may not be able to deliver,
you may block the quotations using the module sale_exceptions_ .
Once this module is installed, go to "Sales > Configuration > Sales > Exceptions rules" and create a new rule using the following code:

.. code-block:: python
if line.product_id and line.product_id.type == 'product' and line.product_id.immediately_usable_qty > line.product_uom_qty:
failed=True
.. _sale_exceptions: https://www.odoo.com/apps/modules/8.0/sale_exceptions/

Known issues / Roadmap
======================

This module does not warn salespersons when the quantity available to promise
is insufficient to deliver a sale order line.
Changed features
----------------
The quoted quantity is now returned as a positive value, whereas it was returned as a negative value before v8.0.
This change was made to ensure consistency with the standard, which used to present outgoing quantities as negative numbers until v8.0, and now presents them as positive numbers instead.

Work to add this feature is underway: https://github.com/OCA/stock-logistics-warehouse/pull/25
Removed features
----------------
Previous versions of this module used to let programmers demand to get the quoted quantity in an arbitrary Unit of Measure using the `context`. This feature was present in the standard computations too until v8.0, but it has been dropped from the standard from v8.0 on.
For the sake of consistency the quoted quantity is now always reported in the product's main Unit of Measure too.

Credits
=======
Expand Down
2 changes: 1 addition & 1 deletion stock_available_sale/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
#
##############################################################################

from . import product
from . import models
7 changes: 2 additions & 5 deletions stock_available_sale/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

{
'name': 'Quotations in quantity available to promise',
'version': '2.0',
'version': '8.0.3.0',
"author": u"Numérigraphe,Odoo Community Association (OCA)",
'category': 'Hidden',
'depends': [
Expand All @@ -29,10 +29,7 @@
'sale_stock',
],
'data': [
'product_view.xml',
],
'test': [
'test/quoted_qty.yml',
'views/product_template_view.xml',
],
'license': 'AGPL-3',
'installable': False,
Expand Down
22 changes: 22 additions & 0 deletions stock_available_sale/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from . import product_template
from . import product_product
133 changes: 133 additions & 0 deletions stock_available_sale/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from openerp import models, api, fields
import openerp.addons.decimal_precision as dp


class ProductProduct(models.Model):
"""Add the computation for the stock available to promise"""
_inherit = 'product.product'

quoted_qty = fields.Float(
compute='_get_quoted_qty',
type='float',
digits_compute=dp.get_precision('Product Unit of Measure'),
string='Quoted',
help="Total quantity of this Product that have been included in "
"Quotations (Draft Sale Orders).\n"
"In a context with a single Warehouse, this includes "
"Quotation processed in this Warehouse.\n"
"In a context with a single Stock Location, this includes "
"Quotation processed at any Warehouse using "
"this Location, or any of its children, as it's Stock "
"Location.\n"
"Otherwise, this includes every Quotation.")

@api.multi
@api.depends('quoted_qty')
def _immediately_usable_qty(self):
"""Subtract quoted quantity from qty available to promise"""
super(ProductProduct, self)._immediately_usable_qty()
for product in self:
product.immediately_usable_qty -= product.quoted_qty

@api.multi
def _get_quoted_qty(self):
"""Compute the quantities in Quotations."""

domain = [
('state', '=', 'draft'),
('product_id', 'in', [p.id for p in self])]

#  Limit to a specific company
if self.env.context.get('force_company', False):
domain.append(('company_id', '=',
self.env.context['force_company']))
# when we search locations, should children be searched too?
if self.env.context.get('compute_child', True):
loc_op = 'child_of'
else:
loc_op = 'in'
# Limit to some locations
# Take warehouses that have these locations as stock locations
if self.env.context.get('location', False):
# Search by ID
if isinstance(self.env.context['location'], (int, long)):
domain.append(
('order_id.warehouse_id.lot_stock_id', loc_op,
[self.env.context['location']]))
# Search by name
elif isinstance(self.env.context['location'], basestring):
location_ids = [
l.id
for l in self.env['stock.location'].search([
('complete_name', 'ilike',
self.env.context['location'])])]
domain.append(
('order_id.warehouse_id.lot_stock_id', loc_op,
location_ids))
# Search by whatever the context has - probably a list of IDs
else:
domain.append(
('order_id.warehouse_id.lot_stock_id', loc_op,
self.env.context['location']))
# Limit to a warehouse
if self.env.context.get('warehouse', False):
domain.append(
('order_id.warehouse_id', '=', self.env.context['warehouse']))
# Limit to a period
from_date = self.env.context.get('from_date', False)
to_date = self.env.context.get('to_date', False)
if from_date:
domain.extend([
('order_id.requested_date', '>=', from_date),
'&', # only consider 'date' when 'equested_date' is empty
('order_id.requested_date', '=', False),
('order_id.date', '>=', from_date),
])
if to_date:
domain.extend([
('order_id.requested_date', '<=', to_date),
'&', # only consider 'date' when 'equested_date' is empty
('order_id.requested_date', '=', False),
('order_id.date', '<=', to_date),
])

# Compute the quoted quantity for each product
results = {}
for group in self.env['sale.order.line'].read_group(
domain, ['product_uom_qty', 'product_id', 'product_uom'],
['product_id', 'product_uom'],
lazy=False):
product_id = group['product_id'][0]
uom_id = group['product_uom'][0]
# Compute the quoted quantity
# Convert to the product's UoM
# Rounding is OK since small values have not been squashed before
results[product_id] = (
results.get(product_id, 0.0) +
self.env['product.uom'].sudo()._compute_qty(
uom_id,
group['product_uom_qty'],
self.browse(product_id).uom_id.id))

for product in self:
product.quoted_qty = results.get(product.id, 0.0)
49 changes: 49 additions & 0 deletions stock_available_sale/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from openerp import models, fields, api
from openerp.addons import decimal_precision as dp


class ProductTemplate(models.Model):
_inherit = 'product.template'

quoted_qty = fields.Float(
compute='_get_quoted_qty',
type='float',
digits_compute=dp.get_precision('Product Unit of Measure'),
string='Quoted',
help="Total quantity of this Product that have been included in "
"Quotations (Draft Sale Orders).\n"
"In a context with a single Warehouse, this includes "
"Quotation processed in this Warehouse.\n"
"In a context with a single Stock Location, this includes "
"Quotation processed at any Warehouse using "
"this Location, or any of its children, as it's Stock "
"Location.\n"
"Otherwise, this includes every Quotation.")

@api.multi
@api.depends('product_variant_ids.quoted_qty')
def _get_quoted_qty(self):
"""Compute the quantity using all the variants"""
for tmpl in self:
tmpl.quoted_qty = sum(
[v.quoted_qty for v in tmpl.product_variant_ids])

0 comments on commit e4daf3d

Please sign in to comment.