Merge Stock available immediately2

This commit is contained in:
Alexandre Fayolle
2015-06-22 17:33:18 +02:00
32 changed files with 1467 additions and 200 deletions

View File

@@ -1,142 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright 2010-2012 Camptocamp SA
# Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
#
# 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.addons import decimal_precision as dp
from openerp.osv import orm, fields
class product_immediately_usable(orm.Model):
"""
Inherit Product in order to add an "immediately usable quantity"
stock field
Immediately usable quantity is : real stock - outgoing qty
"""
_inherit = 'product.product'
def _product_available(self, cr, uid, ids, field_names=None,
arg=False, context=None):
"""
Get super() _product_available and compute immediately_usable_qty
"""
# We need available and outgoing quantities to compute
# immediately usable quantity.
# When immediately_usable_qty is displayed but
# not qty_available and outgoing_qty,
# they are not computed in the super method so we cannot
# compute immediately_usable_qty.
# To avoid this issue, we add the 2 fields in
# field_names to compute them.
if 'immediately_usable_qty' in field_names:
field_names.append('qty_available')
field_names.append('outgoing_qty')
res = super(product_immediately_usable, self)._product_available(
cr, uid, ids, field_names, arg, context)
if 'immediately_usable_qty' in field_names:
for product_id, stock_qty in res.iteritems():
res[product_id]['immediately_usable_qty'] = \
stock_qty['qty_available'] + stock_qty['outgoing_qty']
return res
_columns = {
'qty_available': fields.function(
_product_available,
multi='qty_available',
type='float',
digits_compute=dp.get_precision('Product UoM'),
string='Quantity On Hand',
help="Current quantity of products.\n"
"In a context with a single Stock Location, this includes "
"goods stored at this Location, or any of its children.\n"
"In a context with a single Warehouse, this includes "
"goods stored in the Stock Location of this Warehouse, "
"or any "
"of its children.\n"
"In a context with a single Shop, this includes goods "
"stored in the Stock Location of the Warehouse of this Shop, "
"or any of its children.\n"
"Otherwise, this includes goods stored in any Stock Location "
"typed as 'internal'."),
'virtual_available': fields.function(
_product_available,
multi='qty_available',
type='float',
digits_compute=dp.get_precision('Product UoM'),
string='Quantity Available',
help="Forecast quantity (computed as Quantity On Hand "
"- Outgoing + Incoming)\n"
"In a context with a single Stock Location, this includes "
"goods stored at this Location, or any of its children.\n"
"In a context with a single Warehouse, this includes "
"goods stored in the Stock Location of this Warehouse, "
"or any "
"of its children.\n"
"In a context with a single Shop, this includes goods "
"stored in the Stock Location of the Warehouse of this Shop, "
"or any of its children.\n"
"Otherwise, this includes goods stored in any Stock Location "
"typed as 'internal'."),
'incoming_qty': fields.function(
_product_available,
multi='qty_available',
type='float',
digits_compute=dp.get_precision('Product UoM'),
string='Incoming',
help="Quantity of products that are planned to arrive.\n"
"In a context with a single Stock Location, this includes "
"goods arriving to this Location, or any of its children.\n"
"In a context with a single Warehouse, this includes "
"goods arriving to the Stock Location of this Warehouse, or "
"any of its children.\n"
"In a context with a single Shop, this includes goods "
"arriving to the Stock Location of the Warehouse of this "
"Shop, or any of its children.\n"
"Otherwise, this includes goods arriving to any Stock "
"Location typed as 'internal'."),
'outgoing_qty': fields.function(
_product_available,
multi='qty_available',
type='float',
digits_compute=dp.get_precision('Product UoM'),
string='Outgoing',
help="Quantity of products that are planned to leave.\n"
"In a context with a single Stock Location, this includes "
"goods leaving from this Location, or any of its children.\n"
"In a context with a single Warehouse, this includes "
"goods leaving from the Stock Location of this Warehouse, or "
"any of its children.\n"
"In a context with a single Shop, this includes goods "
"leaving from the Stock Location of the Warehouse of this "
"Shop, or any of its children.\n"
"Otherwise, this includes goods leaving from any Stock "
"Location typed as 'internal'."),
'immediately_usable_qty': fields.function(
_product_available,
digits_compute=dp.get_precision('Product UoM'),
type='float',
string='Immediately Usable',
multi='qty_available',
help="Quantity of products really available for sale."
"Computed as: Quantity On Hand - Outgoing."),
}

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
stock available_immediately for OpenERP
Author Guewen Baconnier. Copyright Camptocamp SA
Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
The licence is in the file __openerp__.py
-->
<openerp>
<data>
<record model="ir.ui.view" id="view_normal_stock_active_qty_form">
<field name="name">product.normal.stock.active.qty.form.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
<field name="arch" type="xml">
<field name="virtual_available" position="after">
<newline/>
<field name="immediately_usable_qty" />
</field>
</field>
</record>
<record model="ir.ui.view" id="product_product_tree_view">
<field name="name">product_immediately_usable.product_product_tree_view</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view"/>
<field name="arch" type="xml">
<data>
<tree position="attributes">
<attribute name="colors">red:immediately_usable_qty&lt;0;blue:immediately_usable_qty&gt;=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty&gt;=0 and state not in ('draft', 'end', 'obsolete')</attribute>
</tree>
<field name="virtual_available" position="replace">
<field name="immediately_usable_qty" />
</field>
</data>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,48 @@
Consider the production potential is available to promise
=========================================================
This module takes the potential quantities available for Products in account in
the quantity available to promise, where the "Potential quantity" is the
quantity that can be manufactured with the components immediately at hand.
Known issues
============
The manufacturing delays are not taken into account : this module assumes that
if you have components in stock goods, you can manufacture finished goods
quickly enough.
To avoid overestimating, **only the first level** of Bill of Materials is
considered.
Roadmap
-------
* include all levels of BoM, using `bom_explode`. @gdgellatly gave an example
of how to do it here: https://github.com/OCA/stock-logistics-warehouse/pull/5#issuecomment-66902191
Ideally, we will want to take manufacturing delays into account: we can't
promiss goods to customers if they want them delivered earlier that we can
make them
* add an option (probably as a sub-module) to consider all raw materials as
available if they can be bought from the suppliers in time for the
manufacturing.
Credits
=======
Contributors
------------
* Loïc Bellier (Numérigraphe) <lb@numerigraphe.com>
* Lionel Sausin (Numérigraphe) <ls@numerigraphe.com>
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://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 http://odoo-community.org.

View File

@@ -0,0 +1,21 @@
# -*- 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

View File

@@ -0,0 +1,35 @@
# -*- 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/>.
#
##############################################################################
{
'name': 'Consider the production potential is available to promise',
'version': '2.0',
"author": u"Numérigraphe,Odoo Community Association (OCA)",
'category': 'Hidden',
'depends': ['stock_available', 'mrp'],
'data': [
'product_view.xml',
],
'test': [
'test/potential_qty.yml',
],
'license': 'AGPL-3',
'installable': False
}

View File

@@ -0,0 +1,34 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * stock_available_mrp
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-30 19:29+0000\n"
"PO-Revision-Date: 2014-07-30 19:29+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_available_mrp
#: field:product.product,potential_qty:0
msgid "Potential"
msgstr "Potentiel"
#. module: stock_available_mrp
#: code:_description:0
#: model:ir.model,name:stock_available_mrp.model_product_product
#, python-format
msgid "Product"
msgstr "Article"
#. module: stock_available_mrp
#: help:product.product,potential_qty:0
msgid "Quantity of this Product that could be produced using the materials already at hand, following a single level of the Bills of Materials."
msgstr "Quantité de cet article que l'on pourrait produire en utilisant les produits déjà disponibles, en suivant un seul niveau de nomenclature."

View File

@@ -0,0 +1,34 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * stock_available_mrp
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-30 19:41+0000\n"
"PO-Revision-Date: 2014-07-30 19:41+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_available_mrp
#: field:product.product,potential_qty:0
msgid "Potential"
msgstr ""
#. module: stock_available_mrp
#: code:_description:0
#: model:ir.model,name:stock_available_mrp.model_product_product
#, python-format
msgid "Product"
msgstr ""
#. module: stock_available_mrp
#: help:product.product,potential_qty:0
msgid "Quantity of this Product that could be produced using the materials already at hand, following a single level of the Bills of Materials."
msgstr ""

View File

@@ -0,0 +1,126 @@
# -*- 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 SUPERUSER_ID
from openerp.osv import orm, fields
import openerp.addons.decimal_precision as dp
class product_product(orm.Model):
"""Add the computation for the stock available to promise"""
_inherit = 'product.product'
def _product_available(self, cr, uid, ids, field_names=None, arg=False,
context=None):
"""Quantity available to promise based on components at hand."""
# Compute the core quantities
res = super(product_product, self)._product_available(
cr, uid, ids, field_names=field_names, arg=arg, context=context)
# If we didn't get a field_names list, there's nothing to do
if field_names is None:
return res
if context is None:
context = {}
# Prepare an alternative context without 'uom', to avoid cross-category
# conversions when reading the available stock of components
if 'uom' in context:
context_wo_uom = context.copy()
del context_wo_uom['uom']
else:
context_wo_uom = context
# Compute the production capacity
if any([f in field_names
for f in ['potential_qty', 'immediately_usable_qty']]):
# Compute the potential qty from BoMs with components available
bom_obj = self.pool['mrp.bom']
to_uom = 'uom' in context and self.pool['product.uom'].browse(
cr, SUPERUSER_ID, context['uom'], context=context)
for product in self.browse(cr, uid, ids, context=context):
# _bom_find() returns a single BoM id.
# We will not check any other BoM for this product
bom_id = bom_obj._bom_find(cr, SUPERUSER_ID, product.id,
product.uom_id.id)
if bom_id:
min_qty = self._compute_potential_qty_from_bom(
cr, uid, bom_id, to_uom or product.uom_id,
context=context)
if 'potential_qty' in field_names:
res[product.id]['potential_qty'] += min_qty
if 'immediately_usable_qty' in field_names:
res[product.id]['immediately_usable_qty'] += min_qty
return res
def _compute_potential_qty_from_bom(self, cr, uid, bom_id, to_uom,
context=None):
"""Compute the potential qty from BoMs with components available"""
bom_obj = self.pool['mrp.bom']
uom_obj = self.pool['product.uom']
if context is None:
context = {}
if 'uom' in context:
context_wo_uom = context.copy()
del context_wo_uom['uom']
else:
context_wo_uom = context
min_qty = False
# Browse ignoring the UoM context to avoid cross-category conversions
bom = bom_obj.browse(
cr, uid, [bom_id], context=context_wo_uom)[0]
# store id of final product uom
for component in bom.bom_lines:
# qty available in BOM line's UoM
# XXX use context['uom'] instead?
stock_component_qty = uom_obj._compute_qty_obj(
cr, uid,
component.product_id.uom_id,
component.product_id.virtual_available,
component.product_uom)
# qty we can produce with this component, in the BoM's UoM
bom_uom_qty = (stock_component_qty // component.product_qty
) * bom.product_qty
# Convert back to the reporting default UoM
stock_product_uom_qty = uom_obj._compute_qty_obj(
cr, uid, bom.product_uom, bom_uom_qty,
to_uom)
if min_qty is False:
min_qty = stock_product_uom_qty
elif stock_product_uom_qty < min_qty:
min_qty = stock_product_uom_qty
if min_qty < 0.0:
min_qty = 0.0
return min_qty
_columns = {
'potential_qty': fields.function(
_product_available, method=True, multi='qty_available',
type='float',
digits_compute=dp.get_precision('Product Unit of Measure'),
string='Potential',
help="Quantity of this Product that could be produced using "
"the materials already at hand, following a single level "
"of the Bills of Materials."),
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- Add the quantity available to promise in the product form -->
<record id="view_product_form_potential_qty" model="ir.ui.view">
<field name="name">product.form.potential_qty</field>
<field name="model">product.product</field>
<field name="type">form</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form" />
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='virtual_available']" position="after">
<field name="potential_qty"/>
</xpath>
</data>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,70 @@
- Test the computation of the potential quantity on product_product_16, a product with several multi-line BoMs
- Create a UoM in the category of PCE
- !record {model: product.uom, id: thousand}:
name: Thousand
factor: 0.001
rounding: 0.0001
uom_type: bigger
category_id: product.product_uom_categ_unit
- Receive enough of the first component to run the BoM 1000x, and check that the potential is unchanged
- !python {model: mrp.bom}: |
bom = self.browse(
cr, uid,
self._bom_find(
cr, uid, ref('product.product_product_16'),
ref('product.product_uom_unit')))
assert len(bom.bom_lines)>1, "The test BoM has a single line, two or more are needed for the test"
initial_qty = bom.product_id.potential_qty
component = bom.bom_lines[0]
assert component.product_uom.category_id.id == ref('product.product_uom_categ_unit'), "The first component's UoM is in the wrong category can't test"
self.pool['stock.move'].create(
cr, uid,
{
'name': 'Receive first component',
'product_id': component.product_id.id,
'product_qty': component.product_qty * 1000.0,
'product_uom': component.product_id.uom_id.id,
'location_id': ref('stock.stock_location_suppliers'),
'location_dest_id': ref('stock.stock_location_stock'),
'state': 'done',
})
# Re-read the potential quantity
bom.refresh()
new_qty = bom.product_id.potential_qty
assert new_qty == initial_qty, "Receiving a single component should not change the potential qty (%s instead of %s)" % (new_qty, initial_qty)
- Receive enough of all the components to run the BoM 1000x and check that the potential is correct
- !python {model: mrp.bom}: |
# Select a BoM for product_product_16
bom = self.browse(
cr, uid,
self._bom_find(
cr, uid, ref('product.product_product_16'),
ref('product.product_uom_unit')))
assert len(bom.bom_lines)>1, "The test BoM has a single line, two or more are needed for the test"
initial_qty = bom.product_id.potential_qty
for component in bom.bom_lines:
assert component.product_uom.category_id.id == ref('product.product_uom_categ_unit'), "The first component's UoM is in the wrong category, can't test"
self.pool['stock.move'].create(
cr, uid,
{
'name': 'Receive all components',
'product_id': component.product_id.id,
'product_qty': component.product_qty * 1000.0,
'product_uom': component.product_id.uom_id.id,
'location_id': ref('stock.stock_location_suppliers'),
'location_dest_id': ref('stock.stock_location_stock'),
'state': 'done',
})
# Re-read the potential quantity
bom.refresh()
new_qty = bom.product_id.potential_qty
right_qty = initial_qty + bom.product_qty * 1000.0
assert new_qty == right_qty, "The potential qty is incorrect after receiveing all the components (%s instead of %s)" % (new_qty, right_qty)
# Re-read the potential quantity with a different UoM in the context
new_qty = self.browse(
cr, uid, bom.id, context={'uom': ref('thousand')}).product_id.potential_qty
right_qty = initial_qty / 1000.0 + bom.product_qty
assert abs(new_qty - right_qty) < 0.0001, "The potential qty is incorrect with another UoM in the context (%s instead of %s)" % (new_qty, right_qty)

View File

@@ -0,0 +1,38 @@
Quotations in quantity available to promise
===========================================
This module computes the quoted quantity of each Product, and subtracts it from
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.
Known issues / Roadmap
======================
This module does not warn salespersons when the quantity available to promise
is insufficient to deliver a sale order line.
Work to add this feature is underway: https://github.com/OCA/stock-logistics-warehouse/pull/25
Credits
=======
Contributors
------------
* Loïc Bellier (Numérigraphe) <lb@numerigraphe.com>
* Lionel Sausin (Numérigraphe) <ls@numerigraphe.com>
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://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 http://odoo-community.org.

View File

@@ -0,0 +1,21 @@
# -*- 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

View File

@@ -0,0 +1,38 @@
# -*- 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/>.
#
##############################################################################
{
'name': 'Quotations in quantity available to promise',
'version': '2.0',
"author": u"Numérigraphe,Odoo Community Association (OCA)",
'category': 'Hidden',
'depends': [
'stock_available',
'sale_order_dates',
'sale_stock',
],
'data': [
'product_view.xml',
],
'test': [
'test/quoted_qty.yml',
],
'license': 'AGPL-3',
}

View File

@@ -0,0 +1,49 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * stock_available_sale
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-30 19:35+0000\n"
"PO-Revision-Date: 2014-07-30 19:35+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_available_sale
#: code:_description:0
#: model:ir.model,name:stock_available_sale.model_product_product
#, python-format
msgid "Product"
msgstr "Article"
#. module: stock_available_sale
#: field:product.product,quoted_qty:0
msgid "Quoted"
msgstr "En devis"
#. module: stock_available_sale
#: code:_description:0
#: model:ir.model,name:stock_available_sale.model_sale_order_line
#, python-format
msgid "Sales Order Line"
msgstr "Ligne de commandes de vente"
#. module: stock_available_sale
#: help:product.product,quoted_qty:0
msgid "Total quantity of this Product that have been included in Quotations (Draft Sale Orders).\n"
"In a context with a single Shop, this includes the Quotation processed at this Shop.\n"
"In a context with a single Warehouse, this includes Quotation processed in any Shop using this Warehouse.\n"
"In a context with a single Stock Location, this includes Quotation processed at any shop using any Warehouse using this Location, or any of its children, as it's Stock Location.\n"
"Otherwise, this includes every Quotation."
msgstr "Quantité totale de cet article se trouvant déjà dans les devis (commandes de vente brouillon).\n"
"Quand le contexte indique une boutique, cela comprend les devis traités dans cette boutique.\n"
"Quand le contexte indique un entrepôt, cela comprend les devis traités dans toutes les boutiques utilisant cet entrepôt.\n"
"Quand le contexte indique un emplacement de stock, cela comprend les devis traités dans toutes les boutiques utilisant un des entrepôt utilisant cet emplacement, ou un de ses enfants, comme emplacement de stock.\n"
"Dans les autres cas, cela comprend tous les devis."

View File

@@ -0,0 +1,45 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * stock_available_sale
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-30 19:42+0000\n"
"PO-Revision-Date: 2014-07-30 19:42+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_available_sale
#: code:_description:0
#: model:ir.model,name:stock_available_sale.model_product_product
#, python-format
msgid "Product"
msgstr ""
#. module: stock_available_sale
#: field:product.product,quoted_qty:0
msgid "Quoted"
msgstr ""
#. module: stock_available_sale
#: code:_description:0
#: model:ir.model,name:stock_available_sale.model_sale_order_line
#, python-format
msgid "Sales Order Line"
msgstr ""
#. module: stock_available_sale
#: help:product.product,quoted_qty:0
msgid "Total quantity of this Product that have been included in Quotations (Draft Sale Orders).\n"
"In a context with a single Shop, this includes the Quotation processed at this Shop.\n"
"In a context with a single Warehouse, this includes Quotation processed in any Shop using this Warehouse.\n"
"In a context with a single Stock Location, this includes Quotation processed at any shop using any Warehouse using this Location, or any of its children, as it's Stock Location.\n"
"Otherwise, this includes every Quotation."
msgstr ""

View File

@@ -0,0 +1,232 @@
# -*- 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 SUPERUSER_ID
from openerp.osv import orm, fields
import openerp.addons.decimal_precision as dp
# Function which uses the pool to call the method from the other modules too.
from openerp.addons.stock_available import _product_available_fnct
class ProductProduct(orm.Model):
"""Add the computation for the stock available to promise"""
_inherit = 'product.product'
def _product_available(self, cr, uid, ids, field_names=None, arg=False,
context=None):
"""Compute the quantities in Quotations."""
# Compute the core quantities
res = super(ProductProduct, self)._product_available(
cr, uid, ids, field_names=field_names, arg=arg, context=context)
# If we didn't get a field_names list, there's nothing to do
if field_names is None:
return res
if context is None:
context = {}
# Prepare an alternative context without 'uom', to avoid cross-category
# conversions when reading the available stock of components
if 'uom' in context:
context_wo_uom = context.copy()
del context_wo_uom['uom']
else:
context_wo_uom = context
# Compute the quantities quoted/available to promise
if any([f in field_names
for f in ['quoted_qty', 'immediately_usable_qty']]):
date_str, date_args = self._get_dates(cr, uid, ids,
context=context)
# Limit the search to some shops according to the context
shop_str, shop_args = self._get_shops(cr, uid, ids,
context=context)
# Query the total by Product and UoM
cr.execute(
"""
SELECT sum(product_uom_qty), product_id, product_uom
FROM sale_order_line
INNER JOIN sale_order
ON (sale_order_line.order_id = sale_order.id)
WHERE product_id in %s
AND sale_order_line.state = 'draft' """ +
date_str + shop_str +
"GROUP BY sale_order_line.product_id, product_uom",
(tuple(ids),) + date_args + shop_args)
results = cr.fetchall()
# Get the UoM resources we'll need for conversion
# UoMs from the products
uoms_o = {}
product2uom = {}
for product in self.browse(cr, uid, ids, context=context):
product2uom[product.id] = product.uom_id
uoms_o[product.uom_id.id] = product.uom_id
# UoM from the results and the context
uom_obj = self.pool['product.uom']
uoms = map(lambda stock_product_uom_qty: stock_product_uom_qty[2],
results)
if context.get('uom', False):
uoms.append(context['uom'])
uoms = filter(lambda stock_product_uom_qty:
stock_product_uom_qty not in uoms_o.keys(), uoms)
if uoms:
uoms = uom_obj.browse(cr, SUPERUSER_ID, list(set(uoms)),
context=context)
for o in uoms:
uoms_o[o.id] = o
# Compute the quoted quantity
for (amount, prod_id, prod_uom) in results:
# Convert the amount to the product's UoM without rounding
amount = amount / uoms_o[prod_uom].factor
if 'quoted_qty' in field_names:
res[prod_id]['quoted_qty'] -= amount
if 'immediately_usable_qty' in field_names:
res[prod_id]['immediately_usable_qty'] -= amount
# Round and optionally convert the results to the requested UoM
for prod_id, stock_qty in res.iteritems():
if context.get('uom', False):
# Convert to the requested UoM
res_uom = uoms_o[context['uom']]
else:
# The conversion is unneeded but we do need the rounding
res_uom = product2uom[prod_id]
if 'quoted_qty' in field_names:
stock_qty['quoted_qty'] = uom_obj._compute_qty_obj(
cr, SUPERUSER_ID, product2uom[prod_id],
stock_qty['quoted_qty'],
res_uom)
if 'immediately_usable_qty' in field_names:
stock_qty['immediately_usable_qty'] = \
uom_obj._compute_qty_obj(
cr, SUPERUSER_ID, product2uom[prod_id],
stock_qty['immediately_usable_qty'],
res_uom)
return res
def _get_shops(self, cr, uid, ids, context=None):
"""Find the shops matching the current context
See the helptext for the field quoted_qty for details"""
shop_ids = []
# Account for one or several locations in the context
# Take any shop using any warehouse that has these locations as stock
# location
if context.get('location', False):
# Either a single or multiple locations can be in the context
if isinstance(context['location'], (int, long)):
location_ids = [context['location']]
else:
location_ids = context['location']
# Add the children locations
if context.get('compute_child', True):
child_location_ids = self.pool['stock.location'].search(
cr, SUPERUSER_ID,
[('location_id', 'child_of', location_ids)])
location_ids = child_location_ids or location_ids
# Get the corresponding Shops
cr.execute(
"""
SELECT id FROM sale_shop
WHERE warehouse_id IN (
SELECT id
FROM stock_warehouse
WHERE lot_stock_id IN %s)""",
(tuple(location_ids),))
res_location = cr.fetchone()
if res_location:
shop_ids.append(res_location)
# Account for a warehouse in the context
# Take any draft order in any shop using this warehouse
if context.get('warehouse', False):
cr.execute("SELECT id "
"FROM sale_shop "
"WHERE warehouse_id = %s",
(int(context['warehouse']),))
res_wh = cr.fetchone()
if res_wh:
shop_ids.append(res_wh)
# If we are in a single Shop context, only count the quotations from
# this shop
if context.get('shop', False):
shop_ids.append(context['shop'])
# Build the SQL to restrict to the selected shops
shop_str = ''
if shop_ids:
shop_str = 'AND sale_order.shop_id IN %s'
if shop_ids:
shop_ids = (tuple(shop_ids),)
else:
shop_ids = ()
return shop_str, shop_ids
def _get_dates(self, cr, uid, ids, context=None):
"""Build SQL criteria to match the context's from/to dates"""
# If we are in a context with dates, only consider the quotations to be
# delivered at these dates.
# If no delivery date was entered, use the order date instead
if not context:
return '', ()
from_date = context.get('from_date', False)
to_date = context.get('to_date', False)
date_str = ''
date_args = []
if from_date:
date_str = """AND COALESCE(
sale_order.requested_date,
sale_order.date_order) >= %s """
date_args.append(from_date)
if to_date:
date_str += """AND COALESCE(
sale_order.requested_date,
sale_order.date_order) <= %s """
date_args.append(to_date)
if date_args:
date_args = (tuple(date_args),)
else:
date_args = ()
return date_str, date_args
_columns = {
'quoted_qty': fields.function(
_product_available_fnct, method=True, multi='qty_available',
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 Shop, this includes the "
"Quotation processed at this Shop.\n"
"In a context with a single Warehouse, this includes "
"Quotation processed in any Shop using this Warehouse.\n"
"In a context with a single Stock Location, this includes "
"Quotation processed at any shop using any Warehouse using "
"this Location, or any of its children, as it's Stock "
"Location.\n"
"Otherwise, this includes every Quotation."),
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- Add the quantity available to promise in the product form -->
<record id="view_product_form_quoted_qty" model="ir.ui.view">
<field name="name">product.form.quoted_qty</field>
<field name="model">product.product</field>
<field name="type">form</field>
<field name="inherit_id" ref="stock_available.view_stock_available_form" />
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='immediately_usable_qty']" position="after">
<field name="quoted_qty"/>
</xpath>
</data>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,66 @@
- Test the computation of the quoted quantity on product.product_product_10
- Create a UoM in the category of PCE
- !record {model: product.uom, id: thousand}:
name: Thousand
factor: 0.001
rounding: 0.00
uom_type: bigger
category_id: product.product_uom_categ_unit
- Cancel all the previous Quotations
- !python {model: sale.order}: |
line_ids = self.pool['sale.order.line'].search(
cr, uid, [('product_id', '=', ref('product.product_product_10')),
('state', '=', 'draft')])
ids = [l.order_id.id for l in self.pool['sale.order.line'].browse(cr, uid, line_ids)]
if ids:
self.action_cancel(cr, uid, ids)
- The quoted quantity should be 0
- !assert {model: product.product, id: product.product_product_10, string: "Check quoted_qty"}:
- quoted_qty == 0.0
- Enter a Quotation
- !record {model: sale.order, id: order1}:
order_line:
- name: Quotation 1
product_uom: product.product_uom_unit
product_uom_qty: 107.0
state: draft
product_id: product.product_product_10
partner_id: base.res_partner_2
partner_invoice_id: base.res_partner_address_8
partner_shipping_id: base.res_partner_address_8
pricelist_id: product.list0
- The quoted qty should match the single quotation
- !assert {model: product.product, id: product.product_product_10, string: "Check quoted_qty"}:
- quoted_qty == -107.0
- Enter another Quotation
- !record {model: sale.order, id: order2}:
order_line:
- name: Quotation 1
product_uom: thousand
product_uom_qty: 0.613
state: draft
product_id: product.product_product_10
partner_id: base.res_partner_2
partner_invoice_id: base.res_partner_address_9
partner_shipping_id: base.res_partner_address_9
pricelist_id: product.list0
- The quoted qty should match the total of the quotations
- !assert {model: product.product, id: product.product_product_10, string: "Check quoted quantity"}:
- quoted_qty == -720.0
- Use the context to report in another UoM
- !assert {model: product.product, id: product.product_product_10, string: "Check in other UoM", context: "{'uom': ref('thousand')}"}:
- quoted_qty == -0.72
- Use the context to report in the default UoM
- !assert {model: product.product, id: product.product_product_10, string: "Check in False UoM", context: "{'uom': False}"}:
- quoted_qty == -720.0
- Confirm one of the Quotations
- !workflow {model: sale.order, action: order_confirm, ref: order1}
- The quoted qty should match the remaining quotation
- !assert {model: product.product, id: product.product_product_10, string: "Check quoted quantity"}:
- quoted_qty == -613.0

View File

@@ -0,0 +1,47 @@
Stock available to promise
==========================
This module proposes several options to compute the quantity available to
promise for each product.
This quantity is based on the projected stock and, depending on the
configuration, it can account for various data such as sales quotations or
immediate production capacity.
This can be configured in the menu Settings > Configuration > Warehouse.
Configuration
=============
By default, this module computes the stock available to promise as the virtual
stock.
To take davantage of the additional features, you must which information you
want to base the computation, by checking one or more boxes in the settings:
`Configuration` > `Warehouse` > `Stock available to promise`.
Usage
=====
This module adds a field named `Available for sale` on the Product form.
Various additional fields may be added, depending on which information you
chose to base the computation on.
Credits
=======
Contributors
------------
* Lionel Sausin (Numérigraphe) <ls@numerigraphe.com>
* many others contributed to sub-modules, please refer to each sub-module's credits
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://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 http://odoo-community.org.

View File

@@ -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
from . import res_config

View File

@@ -0,0 +1,32 @@
# -*- 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/>.
#
##############################################################################
{
'name': 'Stock available to promise',
'version': '2.0',
"author": u"Numérigraphe,Odoo Community Association (OCA)",
'category': 'Warehouse',
'depends': ['stock'],
'license': 'AGPL-3',
'data': [
'product_view.xml',
'res_config_view.xml',
]
}

View File

@@ -0,0 +1,98 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * stock_available
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-30 16:42+0000\n"
"PO-Revision-Date: 2014-07-30 16:42+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_available
#: field:product.product,immediately_usable_qty:0
msgid "Available to promise"
msgstr "Disponible à la vente"
#. module: stock_available
#: view:product.product:0
msgid "Available to promise:"
msgstr "Disponible à la vente:"
#. module: stock_available
#: field:stock.config.settings,module_stock_available_sale:0
msgid "Exclude goods already in sale quotations"
msgstr "Exclure les marchandises qui sont déjà dans les devis"
#. module: stock_available
#: field:stock.config.settings,module_stock_available_immediately:0
msgid "Exclude incoming goods"
msgstr "Exclure les receptions attendues"
#. module: stock_available
#: field:stock.config.settings,module_stock_available_mrp:0
msgid "Include the production potential"
msgstr "Inclure la production potentielle"
#. module: stock_available
#: code:_description:0
#: model:ir.model,name:stock_available.model_product_product
#, python-format
msgid "Product"
msgstr "Article"
#. module: stock_available
#: view:stock.config.settings:0
msgid "Stock available to promise"
msgstr "Stock disponible à la vente"
#. module: stock_available
#: help:product.product,immediately_usable_qty:0
msgid "Stock for this Product that can be safely proposed for sale to Customers.\n"
"The definition of this value can be configured to suit your needs"
msgstr "Stock de cet article qui peut sans risque être proposé à la vente aux clients.\n"
"La définition de cette valeur est paramétrable selon vos besoins"
#. module: stock_available
#: help:stock.config.settings,module_stock_available_mrp:0
msgid "This will add the quantities of goods that can be immediately manufactured, to the quantities available to promise.\n"
"This installs the module stock_available_mrp.\n"
"If the module mrp is not installed, this will install it too"
msgstr "Ceci ajoute les quantités de marchandises qui peuvent être immédiatement fabriquées, aux quantitiés disponibles à la vente.\n"
"Ceci installe le module stock_available_mrp.\n"
"Si le module mrp n'est pas encore installé, il le sera aussi"
#. module: stock_available
#: help:stock.config.settings,module_stock_available_immediately:0
msgid "This will subtract incoming quantities from the quantities available to promise.\n"
"This installs the module stock_available_immediately."
msgstr "Ceci soustrait les réceptions attendues des quantitiés disponibles à la vente.\n"
"Ceci installe le module stock_available_immediately."
#. module: stock_available
#: help:stock.config.settings,module_stock_available_sale:0
msgid "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"
msgstr "Ceci soustrait les quantités des devis de vente des quantitiés disponibles à la vente.\n"
"Ceci installe le modules stock_available_sale.\n"
"Si les modules sale et sale_delivery_date ne sont pas encore installés, ils le seront également"
#. module: stock_available
#: view:product.product:0
msgid "red:immediately_usable_qty<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=0 and state not in ('draft', 'end', 'obsolete')"
msgstr "red:immediately_usable_qty<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=0 and state not in ('draft', 'end', 'obsolete')"
#. module: stock_available
#: code:_description:0
#: model:ir.model,name:stock_available.model_stock_config_settings
#, python-format
msgid "stock.config.settings"
msgstr "stock.config.settings"

View File

@@ -0,0 +1,92 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * stock_available
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-30 16:48+0000\n"
"PO-Revision-Date: 2014-07-30 16:48+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_available
#: field:product.product,immediately_usable_qty:0
msgid "Available to promise"
msgstr ""
#. module: stock_available
#: view:product.product:0
msgid "Available to promise:"
msgstr ""
#. module: stock_available
#: field:stock.config.settings,module_stock_available_sale:0
msgid "Exclude goods already in sale quotations"
msgstr ""
#. module: stock_available
#: field:stock.config.settings,module_stock_available_immediately:0
msgid "Exclude incoming goods"
msgstr ""
#. module: stock_available
#: field:stock.config.settings,module_stock_available_mrp:0
msgid "Include the production potential"
msgstr ""
#. module: stock_available
#: code:_description:0
#: model:ir.model,name:stock_available.model_product_product
#, python-format
msgid "Product"
msgstr ""
#. module: stock_available
#: view:stock.config.settings:0
msgid "Stock available to promise"
msgstr ""
#. module: stock_available
#: help:product.product,immediately_usable_qty:0
msgid "Stock for this Product that can be safely proposed for sale to Customers.\n"
"The definition of this value can be configured to suit your needs"
msgstr ""
#. module: stock_available
#: help:stock.config.settings,module_stock_available_mrp:0
msgid "This will add the quantities of goods that can be immediately manufactured, to the quantities available to promise.\n"
"This installs the module stock_available_mrp.\n"
"If the module mrp is not installed, this will install it too"
msgstr ""
#. module: stock_available
#: help:stock.config.settings,module_stock_available_immediately:0
msgid "This will subtract incoming quantities from the quantities available to promise.\n"
"This installs the module stock_available_immediately."
msgstr ""
#. module: stock_available
#: help:stock.config.settings,module_stock_available_sale:0
msgid "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"
msgstr ""
#. module: stock_available
#: view:product.product:0
msgid "red:immediately_usable_qty<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=0 and state not in ('draft', 'end', 'obsolete')"
msgstr ""
#. module: stock_available
#: code:_description:0
#: model:ir.model,name:stock_available.model_stock_config_settings
#, python-format
msgid "stock.config.settings"
msgstr ""

View File

@@ -0,0 +1,85 @@
# -*- 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 Product(models.Model):
"""Add a field for the stock available to promise.
Useful implementations need to be installed through the Settings menu or by
installing one of the modules stock_available_*
"""
_inherit = 'product.product'
@api.depends('virtual_available')
def _immediately_usable_qty(self):
"""No-op implementation of the stock available to promise.
By default, available to promise = forecasted quantity.
Must be overridden by another module that actually implement
computations."""
for product in self:
product.immediately_usable_qty = product.virtual_available
immediately_usable_qty = fields.Float(
digits=dp.get_precision('Product Unit of Measure'),
compute='_immediately_usable_qty',
string='Available to promise',
help="Stock for this Product that can be safely proposed "
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs")
class ProductTemplate(models.Model):
"""Add a field for the stock available to promise.
Useful implementations need to be installed through the Settings menu or by
installing one of the modules stock_available_*
"""
_inherit = 'product.template'
@api.depends('virtual_available')
def _immediately_usable_qty(self):
"""No-op implementation of the stock available to promise.
By default, available to promise = forecasted quantity.
Must be overridden by another module that actually implement
computations."""
product_model = self.env['product.product']
for product_template in self:
products = product_model.search([(
'product_tmpl_id', '=', product_template.id)])
qty = 0
for product in products:
qty += product.immediately_usable_qty
product_template.immediately_usable_qty = qty
immediately_usable_qty = fields.Float(
digits=dp.get_precision('Product Unit of Measure'),
compute='_immediately_usable_qty',
string='Available to promise',
help="Stock for this Product that can be safely proposed "
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs")

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_stock_available_form">
<field name="name">Quantity available to promise (form)</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.view_template_property_form"/>
<field name="arch" type="xml">
<field name="virtual_available" position="after">
<newline/>
<field name="immediately_usable_qty" />
</field>
</field>
</record>
<record model="ir.ui.view" id="view_stock_available_tree">
<field name="name">Quantity available to promise (tree)</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.view_stock_product_template_tree"/>
<field name="arch" type="xml">
<data>
<tree position="attributes">
<attribute name="colors">red:immediately_usable_qty&lt;0;blue:immediately_usable_qty&gt;=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty&gt;=0 and state not in ('draft', 'end', 'obsolete')</attribute>
</tree>
<field name="virtual_available" position="after">
<field name="immediately_usable_qty" />
</field>
</data>
</field>
</record>
<record model="ir.ui.view" id="view_stock_available_kanban">
<field name="name">Quantity available to promise (kanban)</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.product_template_kanban_stock_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='virtual_available']/.." position="replace">
<li t-if="record.type.raw_value != 'service'">Available to promise: <field name="immediately_usable_qty"/> <field name="uom_id"/></li>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@@ -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 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields
class StockConfig(models.TransientModel):
"""Add options to easily install the submodules"""
_inherit = 'stock.config.settings'
module_stock_available_immediately = fields.Boolean(
string='Exclude incoming goods',
help="This will subtract incoming quantities from the quantities "
"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_mrp = fields.Boolean(
# string='Include the production potential',
# help="This will add the quantities of goods that can be "
# "immediately manufactured, to the quantities available to "
# "promise.\n"
# "This installs the module stock_available_mrp.\n"
# "If the module mrp is not installed, this will install it "
# "too")

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_stock_configuration" model="ir.ui.view">
<field name="name">Stock settings: quantity available to promise</field>
<field name="model">stock.config.settings</field>
<field name="inherit_id" ref="stock.view_stock_config_settings" />
<field name="arch" type="xml">
<data>
<xpath expr="//group[last()]" position="after">
<group>
<label for="id" string="Stock available to promise" />
<div>
<div>
<field name="module_stock_available_immediately" class="oe_inline" />
<label for="module_stock_available_immediately" />
</div>
<!-- <div>
<field name="module_stock_available_sale" class="oe_inline" />
<label for="module_stock_available_sale" />
</div> -->
<!-- <div>
<field name="module_stock_available_mrp" class="oe_inline" />
<label for="module_stock_available_mrp" />
</div> -->
</div>
</group>
</xpath>
</data>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,30 @@
Ignore planned receptions in quantity available to promise
==========================================================
Normally the quantity available to promise is based on the virtual stock,
which includes both planned outgoing and incoming goods.
This module will subtract the planned receptions from the quantity available to
promise.
Credits
=======
Contributors
------------
* Author: Guewen Baconnier (Camptocamp SA)
* Sébastien BEAU (Akretion) <sebastien.beau@akretion.com>
* Lionel Sausin (Numérigraphe) <ls@numerigraphe.com>
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://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 http://odoo-community.org.

View File

@@ -20,21 +20,12 @@
#
#
{
"name": "Immediately Usable Stock Quantity",
"version": "1.0",
"depends": ["product", "stock", ],
"name": "Ignore planned receptions in quantity available to promise",
"version": "2.0",
"depends": ["stock_available"],
"author": "Camptocamp,Odoo Community Association (OCA)",
"license": "AGPL-3",
"description": """
Compute the immediately usable stock.
Immediately usable is computed : Quantity on Hand - Outgoing Stock.
""",
"website": "http://tinyerp.com/module_account.html",
"category": "Generic Modules/Stock",
"data": ["product_view.xml",
],
"active": False,
'installable': False
"category": "Hidden",
'installable': True
}

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright 2010-2012 Camptocamp SA
# Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
#
# 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
class Product(models.Model):
"""Subtract incoming qty from immediately_usable_qty"""
_inherit = 'product.product'
def _immediately_usable_qty(self):
"""Ignore the incoming goods in the quantity available to promise"""
super(Product, self)._immediately_usable_qty()
for product in self:
product.immediately_usable_qty -= product.incoming_qty

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record model="ir.ui.view" id="product_template_form_view_reservation_button">
<field name="name">product.template.reservation.button</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button" name="action_view_reservations"
type="object" groups="base.group_sale_salesman" icon="fa-lock">
@@ -19,7 +19,7 @@
<record model="ir.ui.view" id="product_product_form_view_reservation_button">
<field name="name">product.template.reservation.button</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button" name="action_view_reservations"