mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
port stock available immediately to 8.0
This commit is contained in:
@@ -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."),
|
||||
}
|
||||
@@ -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<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=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>
|
||||
48
__unported__/stock_available_mrp/README.rst
Normal file
48
__unported__/stock_available_mrp/README.rst
Normal 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.
|
||||
21
__unported__/stock_available_mrp/__init__.py
Normal file
21
__unported__/stock_available_mrp/__init__.py
Normal 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
|
||||
35
__unported__/stock_available_mrp/__openerp__.py
Normal file
35
__unported__/stock_available_mrp/__openerp__.py
Normal 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
|
||||
}
|
||||
34
__unported__/stock_available_mrp/i18n/fr.po
Normal file
34
__unported__/stock_available_mrp/i18n/fr.po
Normal 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."
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
126
__unported__/stock_available_mrp/product.py
Normal file
126
__unported__/stock_available_mrp/product.py
Normal 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."),
|
||||
}
|
||||
19
__unported__/stock_available_mrp/product_view.xml
Normal file
19
__unported__/stock_available_mrp/product_view.xml
Normal 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>
|
||||
70
__unported__/stock_available_mrp/test/potential_qty.yml
Normal file
70
__unported__/stock_available_mrp/test/potential_qty.yml
Normal 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)
|
||||
38
__unported__/stock_available_sale/README.rst
Normal file
38
__unported__/stock_available_sale/README.rst
Normal 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.
|
||||
21
__unported__/stock_available_sale/__init__.py
Normal file
21
__unported__/stock_available_sale/__init__.py
Normal 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
|
||||
38
__unported__/stock_available_sale/__openerp__.py
Normal file
38
__unported__/stock_available_sale/__openerp__.py
Normal 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',
|
||||
}
|
||||
49
__unported__/stock_available_sale/i18n/fr.po
Normal file
49
__unported__/stock_available_sale/i18n/fr.po
Normal 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."
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
232
__unported__/stock_available_sale/product.py
Normal file
232
__unported__/stock_available_sale/product.py
Normal 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."),
|
||||
}
|
||||
19
__unported__/stock_available_sale/product_view.xml
Normal file
19
__unported__/stock_available_sale/product_view.xml
Normal 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>
|
||||
66
__unported__/stock_available_sale/test/quoted_qty.yml
Normal file
66
__unported__/stock_available_sale/test/quoted_qty.yml
Normal 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
|
||||
|
||||
47
stock_available/README.rst
Normal file
47
stock_available/README.rst
Normal 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.
|
||||
22
stock_available/__init__.py
Normal file
22
stock_available/__init__.py
Normal 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
|
||||
32
stock_available/__openerp__.py
Normal file
32
stock_available/__openerp__.py
Normal 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',
|
||||
]
|
||||
}
|
||||
98
stock_available/i18n/fr.po
Normal file
98
stock_available/i18n/fr.po
Normal 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"
|
||||
|
||||
92
stock_available/i18n/stock_available.pot
Normal file
92
stock_available/i18n/stock_available.pot
Normal 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 ""
|
||||
|
||||
66
stock_available/product.py
Normal file
66
stock_available/product.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# -*- 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):
|
||||
"""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'
|
||||
|
||||
# immediately usable quantity caluculated with the quant method
|
||||
@api.multi
|
||||
@api.depends('virtual_available')
|
||||
def _immediately_usable_qty(self):
|
||||
stock_location_obj = self.env['stock.location']
|
||||
internal_locations = stock_location_obj.search([
|
||||
('usage', '=', 'internal')])
|
||||
sublocation_ids = []
|
||||
for location in internal_locations:
|
||||
sublocation_ids.append(self.env['stock.location'].search(
|
||||
[('id', 'child_of', location.id)]).ids)
|
||||
for product_template in self:
|
||||
products = self.env['product.product'].search([
|
||||
('product_tmpl_id', '=', product_template.id)])
|
||||
quant_obj = self.env['stock.quant']
|
||||
quants = quant_obj.search([
|
||||
('location_id', 'in', sublocation_ids),
|
||||
('product_id', 'in', products.ids),
|
||||
('reservation_id', '=', False)])
|
||||
availability = 0
|
||||
if quants:
|
||||
for quant in quants:
|
||||
availability += quant.qty
|
||||
product_template.immediately_usable_qty = availability
|
||||
|
||||
immediately_usable_qty = fields.Float(
|
||||
digits=dp.get_precision('Product Unit of Measure'),
|
||||
compute='_immediately_usable_qty',
|
||||
string='Available to promise (quant calculation)',
|
||||
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 , this number is obtained by using the new odoo 8 "
|
||||
"quants, so it gives us the actual current quants minus reserved"
|
||||
"quants")
|
||||
43
stock_available/product_view.xml
Normal file
43
stock_available/product_view.xml
Normal 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<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=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>
|
||||
49
stock_available/res_config.py
Normal file
49
stock_available/res_config.py
Normal 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")
|
||||
33
stock_available/res_config_view.xml
Normal file
33
stock_available/res_config_view.xml
Normal 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>
|
||||
120
stock_available/tests/test_stock_available.py
Normal file
120
stock_available/tests/test_stock_available.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2015 Therp BV <http://therp.nl>
|
||||
#
|
||||
# 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.tests.common import TransactionCase
|
||||
|
||||
|
||||
class testStockLogisticsWarehouse(TransactionCase):
|
||||
|
||||
def test01_stock_levels(self):
|
||||
"""checking that immediately_usable_qty actually reflects \
|
||||
the variations in stock, both on product and template"""
|
||||
moveObj = self.env['stock.move']
|
||||
productObj = self.env['product.product']
|
||||
templateObj = self.env['product.template']
|
||||
supplier_location = self.env.ref('stock.stock_location_suppliers')
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
customer_location = self.env.ref('stock.stock_location_customers')
|
||||
uom_unit = self.env.ref('product.product_uom_unit')
|
||||
|
||||
# Create product template
|
||||
templateAB = templateObj.create(
|
||||
{'name': 'templAB',
|
||||
'uom_id': uom_unit.id,
|
||||
})
|
||||
|
||||
# Create product A and B
|
||||
productA = productObj.create(
|
||||
{'name': 'product A',
|
||||
'standard_price': 1,
|
||||
'type': 'product',
|
||||
'uom_id': uom_unit.id,
|
||||
'default_code': 'A',
|
||||
'product_tmpl_id': templateAB.id,
|
||||
})
|
||||
|
||||
productB = productObj.create(
|
||||
{'name': 'product B',
|
||||
'standard_price': 1,
|
||||
'type': 'product',
|
||||
'uom_id': uom_unit.id,
|
||||
'default_code': 'B',
|
||||
'product_tmpl_id': templateAB.id,
|
||||
})
|
||||
|
||||
# Create a stock move from INCOMING to STOCK
|
||||
stockMoveInA = moveObj.create(
|
||||
{'location_id': supplier_location.id,
|
||||
'location_dest_id': stock_location.id,
|
||||
'name': 'MOVE INCOMING -> STOCK ',
|
||||
'product_id': productA.id,
|
||||
'product_uom': productA.uom_id.id,
|
||||
'product_uom_qty': 2,
|
||||
})
|
||||
|
||||
stockMoveInB = moveObj.create(
|
||||
{'location_id': supplier_location.id,
|
||||
'location_dest_id': stock_location.id,
|
||||
'name': 'MOVE INCOMING -> STOCK ',
|
||||
'product_id': productB.id,
|
||||
'product_uom': productB.uom_id.id,
|
||||
'product_uom_qty': 3,
|
||||
})
|
||||
|
||||
def compare_product_usable_qty(product, value):
|
||||
# Refresh, because the function field is not recalculated between
|
||||
# transactions
|
||||
product.refresh()
|
||||
self.assertEqual(product.immediately_usable_qty, value)
|
||||
|
||||
compare_product_usable_qty(productA, 0)
|
||||
compare_product_usable_qty(templateAB, 0)
|
||||
|
||||
stockMoveInA.action_confirm()
|
||||
compare_product_usable_qty(productA, 0)
|
||||
compare_product_usable_qty(templateAB, 0)
|
||||
|
||||
stockMoveInA.action_assign()
|
||||
compare_product_usable_qty(productA, 0)
|
||||
compare_product_usable_qty(templateAB, 0)
|
||||
|
||||
stockMoveInA.action_done()
|
||||
compare_product_usable_qty(productA, 2)
|
||||
compare_product_usable_qty(templateAB, 2)
|
||||
|
||||
# will directly trigger action_done on productB
|
||||
stockMoveInB.action_done()
|
||||
compare_product_usable_qty(productA, 2)
|
||||
compare_product_usable_qty(productB, 3)
|
||||
compare_product_usable_qty(templateAB, 5)
|
||||
|
||||
# Create a stock move from STOCK to CUSTOMER
|
||||
stockMoveOutA = moveObj.create(
|
||||
{'location_id': stock_location.id,
|
||||
'location_dest_id': customer_location.id,
|
||||
'name': ' STOCK --> CUSTOMER ',
|
||||
'product_id': productA.id,
|
||||
'product_uom': productA.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
'state': 'confirmed',
|
||||
})
|
||||
|
||||
stockMoveOutA.action_done()
|
||||
compare_product_usable_qty(productA, 1)
|
||||
compare_product_usable_qty(templateAB, 4)
|
||||
30
stock_available_immediately/README.rst
Normal file
30
stock_available_immediately/README.rst
Normal 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.
|
||||
@@ -17,5 +17,3 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from . import product
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user