Merge lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp into lp:stock-logistic-warehouse

Proposed by Lionel Sausin - Initiatives/Numérigraphe
Status: Rejected
Rejected by: Yannick Vaucher @ Camptocamp
Proposed branch: lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp
Merge into: lp:stock-logistic-warehouse
Prerequisite: lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-sale
Diff against target: 326 lines (+280/-0)
7 files modified
stock_available/res_config.py (+8/-0)
stock_available/res_config_view.xml (+4/-0)
stock_available_mrp/__init__.py (+21/-0)
stock_available_mrp/__openerp__.py (+39/-0)
stock_available_mrp/product.py (+121/-0)
stock_available_mrp/product_view.xml (+19/-0)
stock_available_mrp/test/potential_qty.yml (+68/-0)
To merge this branch: bzr merge lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp
Reviewer Review Type Date Requested Status
Alexandre Fayolle - camptocamp Needs Resubmitting
Review via email: mp+220764@code.launchpad.net

Description of the change

Add stock_available_mrp: take immediate manufacturing capability into account in the stock quantity available to promise

This branch builds upon lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available, which adds a generic module to compute the stock available to promise in a configurable way.

It also includes lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-sale, which adds another //unrelated// implementation. This is only to avoid merge conflicts in case both are accepted, but they are independent works I can rebase this branch if needed.

Module by Loïc Bellier with contributions from your humble servant.

To post a comment you must log in.
Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

The source code management for this project has been moved to https://github.com/OCA/stock-logistics-warehouse

Could you resubmit this MP on the new site?

review: Needs Resubmitting
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

Unmerged revisions

37. By Numérigraphe

[ADD] stock_available_mrp: take immediate manufaturing capability into account in the stock quantity available to promise

36. By Numérigraphe

[ADD] stock_available_sale: take sale quotations into account in the stock quantity available to promise

35. By Numérigraphe

[ADD] stock_available: generic module to compute the stock quantity available to promise using several implementations. Make stock_available_immediatly the first configurable implementation

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'stock_available/res_config.py'
2--- stock_available/res_config.py 2014-05-23 07:58:49 +0000
3+++ stock_available/res_config.py 2014-05-23 07:58:49 +0000
4@@ -38,4 +38,12 @@
5 "This installs the modules stock_available_sale.\n"
6 "If the modules sale and sale_delivery_date are not "
7 "installed, this will install them too"),
8+ 'module_stock_available_mrp': fields.boolean(
9+ 'Include the production potential',
10+ help="This will add the quantities of goods that can be"
11+ "immediately manufactured, to the quantities available to"
12+ "promise.\n"
13+ "This installs the module stock_available_mrp.\n"
14+ "If the module mrp is not installed, this will install it "
15+ "too"),
16 }
17
18=== modified file 'stock_available/res_config_view.xml'
19--- stock_available/res_config_view.xml 2014-05-23 07:58:49 +0000
20+++ stock_available/res_config_view.xml 2014-05-23 07:58:49 +0000
21@@ -19,6 +19,10 @@
22 <field name="module_stock_available_sale" class="oe_inline" />
23 <label for="module_stock_available_sale" />
24 </div>
25+ <div>
26+ <field name="module_stock_available_mrp" class="oe_inline" />
27+ <label for="module_stock_available_mrp" />
28+ </div>
29 </div>
30 </group>
31 </xpath>
32
33=== added directory 'stock_available_mrp'
34=== added file 'stock_available_mrp/__init__.py'
35--- stock_available_mrp/__init__.py 1970-01-01 00:00:00 +0000
36+++ stock_available_mrp/__init__.py 2014-05-23 07:58:49 +0000
37@@ -0,0 +1,21 @@
38+# -*- coding: utf-8 -*-
39+##############################################################################
40+#
41+# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
42+#
43+# This program is free software: you can redistribute it and/or modify
44+# it under the terms of the GNU Affero General Public License as
45+# published by the Free Software Foundation, either version 3 of the
46+# License, or (at your option) any later version.
47+#
48+# This program is distributed in the hope that it will be useful,
49+# but WITHOUT ANY WARRANTY; without even the implied warranty of
50+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51+# GNU Affero General Public License for more details.
52+#
53+# You should have received a copy of the GNU Affero General Public License
54+# along with this program. If not, see <http://www.gnu.org/licenses/>.
55+#
56+##############################################################################
57+
58+from . import product
59
60=== added file 'stock_available_mrp/__openerp__.py'
61--- stock_available_mrp/__openerp__.py 1970-01-01 00:00:00 +0000
62+++ stock_available_mrp/__openerp__.py 2014-05-23 07:58:49 +0000
63@@ -0,0 +1,39 @@
64+# -*- coding: utf-8 -*-
65+##############################################################################
66+#
67+# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
68+#
69+# This program is free software: you can redistribute it and/or modify
70+# it under the terms of the GNU Affero General Public License as
71+# published by the Free Software Foundation, either version 3 of the
72+# License, or (at your option) any later version.
73+#
74+# This program is distributed in the hope that it will be useful,
75+# but WITHOUT ANY WARRANTY; without even the implied warranty of
76+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
77+# GNU Affero General Public License for more details.
78+#
79+# You should have received a copy of the GNU Affero General Public License
80+# along with this program. If not, see <http://www.gnu.org/licenses/>.
81+#
82+##############################################################################
83+
84+{
85+ 'name': 'Consider the production potential is available to promise',
86+ 'version': '2.0',
87+ 'author': u'Numérigraphe',
88+ 'category': 'Hidden',
89+ 'depends': ['stock_available', 'mrp'],
90+ 'description': """
91+This module takes the potential quantities available for Products in account in
92+the quantity available to promise, where the "Potential quantity" is the
93+quantity that can be manufactured with the components immediately at hand,
94+following a single level of Bill of Materials.""",
95+ 'data': [
96+ 'product_view.xml',
97+ ],
98+ 'test': [
99+ 'test/potential_qty.yml',
100+ ],
101+ 'license': 'AGPL-3',
102+}
103
104=== added file 'stock_available_mrp/product.py'
105--- stock_available_mrp/product.py 1970-01-01 00:00:00 +0000
106+++ stock_available_mrp/product.py 2014-05-23 07:58:49 +0000
107@@ -0,0 +1,121 @@
108+# -*- coding: utf-8 -*-
109+##############################################################################
110+#
111+# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
112+#
113+# This program is free software: you can redistribute it and/or modify
114+# it under the terms of the GNU Affero General Public License as
115+# published by the Free Software Foundation, either version 3 of the
116+# License, or (at your option) any later version.
117+#
118+# This program is distributed in the hope that it will be useful,
119+# but WITHOUT ANY WARRANTY; without even the implied warranty of
120+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
121+# GNU Affero General Public License for more details.
122+#
123+# You should have received a copy of the GNU Affero General Public License
124+# along with this program. If not, see <http://www.gnu.org/licenses/>.
125+#
126+##############################################################################
127+
128+from openerp.osv import orm, fields
129+import decimal_precision as dp
130+
131+
132+class product_product(orm.Model):
133+ """Add the computation for the stock available to promise"""
134+ _inherit = 'product.product'
135+
136+ def _product_available(self, cr, uid, ids, field_names=None, arg=False,
137+ context=None):
138+ """Compute the potential quantities available to promise."""
139+ # Check the context
140+ if context is None:
141+ context = {}
142+ # Prepare an alternative context without 'uom', to avoid cross-category
143+ # conversions when reading the available stock of components
144+ if 'uom' in context:
145+ context_wo_uom = context.copy()
146+ del context_wo_uom['uom']
147+ else:
148+ context_wo_uom = context
149+
150+ if field_names is None:
151+ field_names = []
152+
153+ # Compute the core quantities
154+ res = super(product_product, self)._product_available(
155+ cr, uid, ids, field_names=field_names, arg=arg, context=context)
156+
157+ # Compute the quantities quoted/potential/available to promise
158+ if ('potential_qty' in field_names):
159+ # Compute the potential qty from BoMs with components available
160+ bom_obj = self.pool['mrp.bom']
161+ to_uom = 'uom' in context and self.pool['product.uom'].browse(
162+ cr, uid, context['uom'], context=context)
163+
164+ for product in self.browse(cr, uid, ids, context=context):
165+ # _bom_find() returns a single BoM id.
166+ # We will not check any other BoM for this product
167+ bom_id = bom_obj._bom_find(cr, uid, product.id,
168+ product.uom_id.id)
169+ if bom_id:
170+ min_qty = self._compute_potential_qty_from_bom(
171+ cr, uid, bom_id, to_uom or product.uom_id,
172+ context=context)
173+
174+ res[product.id]['potential_qty'] += min_qty
175+ if ('immediately_usable_qty' in field_names):
176+ res[product.id]['immediately_usable_qty'] += min_qty
177+
178+ return self._update_virtual_available(cr, uid, res, context=context)
179+
180+ def _compute_potential_qty_from_bom(self, cr, uid, bom_id, to_uom,
181+ context):
182+ """Compute the potential qty from BoMs with components available"""
183+ bom_obj = self.pool['mrp.bom']
184+ uom_obj = self.pool['product.uom']
185+ if 'uom' in context:
186+ context_wo_uom = context.copy()
187+ del context_wo_uom['uom']
188+ else:
189+ context_wo_uom = context
190+ min_qty = False
191+ # Browse ignoring the UoM context to avoid cross-category conversions
192+ final_product = bom_obj.browse(
193+ cr, uid, [bom_id], context=context_wo_uom)[0]
194+
195+ # store id of final product uom
196+
197+ for component in final_product.bom_lines:
198+ # qty available in BOM line's UoM
199+ # XXX use context['uom'] instead?
200+ stock_component_qty = uom_obj._compute_qty_obj(
201+ cr, uid,
202+ component.product_id.uom_id,
203+ component.product_id.virtual_available,
204+ component.product_uom)
205+ # qty we can produce with this component, in the BoM's UoM
206+ recipe_uom_qty = (stock_component_qty // component.product_qty
207+ ) * final_product.product_qty
208+ # Convert back to the reporting default UoM
209+ stock_product_uom_qty = uom_obj._compute_qty_obj(
210+ cr, uid, final_product.product_uom, recipe_uom_qty, to_uom)
211+ if min_qty is False:
212+ min_qty = stock_product_uom_qty
213+ elif stock_product_uom_qty < min_qty:
214+ min_qty = stock_product_uom_qty
215+ if min_qty < 0.0:
216+ min_qty = 0.0
217+ return min_qty
218+
219+ _columns = {
220+ 'potential_qty': fields.function(
221+ _product_available, method=True, multi='qty_available',
222+ type='float',
223+ digits_compute=dp.get_precision('Product Unit of Measure'),
224+ string='Potential',
225+ help="Quantity of this Product that could be produced using "
226+ "the materials already at hand, following a single level"
227+ "of the Bills of Materials."),
228+ }
229
230=== added file 'stock_available_mrp/product_view.xml'
231--- stock_available_mrp/product_view.xml 1970-01-01 00:00:00 +0000
232+++ stock_available_mrp/product_view.xml 2014-05-23 07:58:49 +0000
233@@ -0,0 +1,19 @@
234+<?xml version="1.0" encoding="UTF-8"?>
235+<openerp>
236+ <data>
237+ <!-- Add the quantity available to promise in the product form -->
238+ <record id="view_product_form_potential_qty" model="ir.ui.view">
239+ <field name="name">product.form.potential_qty</field>
240+ <field name="model">product.product</field>
241+ <field name="type">form</field>
242+ <field name="inherit_id" ref="stock.view_normal_procurement_locations_form" />
243+ <field name="arch" type="xml">
244+ <data>
245+ <xpath expr="//field[@name='virtual_available']" position="after">
246+ <field name="potential_qty"/>
247+ </xpath>
248+ </data>
249+ </field>
250+ </record>
251+ </data>
252+</openerp>
253
254=== added directory 'stock_available_mrp/test'
255=== added file 'stock_available_mrp/test/potential_qty.yml'
256--- stock_available_mrp/test/potential_qty.yml 1970-01-01 00:00:00 +0000
257+++ stock_available_mrp/test/potential_qty.yml 2014-05-23 07:58:49 +0000
258@@ -0,0 +1,68 @@
259+- Test the computation of the potential quantity on product_product_16, a product with several multi-line BoMs
260+
261+- Create a UoM in the category of PCE
262+- !record {model: product.uom, id: thousand}:
263+ name: Thousand
264+ factor: 0.001
265+ rounding: 0.0001
266+ uom_type: bigger
267+ category_id: product.product_uom_categ_unit
268+
269+- Receive enough of the first component to run the BoM 1000x, and check that the potential is unchanged
270+- !python {model: mrp.bom}: |
271+ bom = self.browse(
272+ cr, uid,
273+ self._bom_find(
274+ cr, uid, ref('product.product_product_16'),
275+ ref('product.product_uom_unit')))
276+ assert len(bom.bom_lines)>1, "The test BoM has a single line, two or more are needed for the test"
277+ initial_qty = bom.product_id.potential_qty
278+ component = bom.bom_lines[0]
279+ 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"
280+ self.pool['stock.move'].create(
281+ cr, uid,
282+ {
283+ 'name': 'Receive first component',
284+ 'product_id': component.product_id.id,
285+ 'product_qty': component.product_qty * 1000.0,
286+ 'product_uom': component.product_id.uom_id.id,
287+ 'location_id': ref('stock.stock_location_suppliers'),
288+ 'location_dest_id': ref('stock.stock_location_stock'),
289+ 'state': 'done',
290+ })
291+ # Re-read the potential quantity
292+ new_qty = self.browse(cr, uid, bom.id).product_id.potential_qty
293+ assert new_qty == initial_qty, "Receiving a single component should not change the potential qty (%s instead of %s)" % (new_qty, initial_qty)
294+
295+- Receive enough of all the components to run the BoM 1000x and check that the potential is correct
296+- !python {model: mrp.bom}: |
297+ # Select a BoM for product_product_16
298+ bom = self.browse(
299+ cr, uid,
300+ self._bom_find(
301+ cr, uid, ref('product.product_product_16'),
302+ ref('product.product_uom_unit')))
303+ assert len(bom.bom_lines)>1, "The test BoM has a single line, two or more are needed for the test"
304+ initial_qty = bom.product_id.potential_qty
305+ for component in bom.bom_lines:
306+ 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"
307+ x = {
308+ 'name': 'Receive all components',
309+ 'product_id': component.product_id.id,
310+ 'product_qty': component.product_qty * 1000.0,
311+ 'product_uom': component.product_id.uom_id.id,
312+ 'location_id': ref('stock.stock_location_suppliers'),
313+ 'location_dest_id': ref('stock.stock_location_stock'),
314+ 'state': 'done',
315+ }
316+ self.pool['stock.move'].create(
317+ cr, uid,x)
318+ # Re-read the potential quantity
319+ new_qty = self.browse(cr, uid, bom.id).product_id.potential_qty
320+ right_qty = initial_qty + bom.product_qty * 1000.0
321+ assert new_qty == right_qty, "The potential qty is incorrect after receiveing all the components (%s instead of %s)" % (new_qty, right_qty)
322+ # Re-read the potential quantity with a different UoM in the context
323+ new_qty = self.browse(
324+ cr, uid, bom.id, context={'uom': ref('thousand')}).product_id.potential_qty
325+ right_qty = initial_qty / 1000.0 + bom.product_qty
326+ 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)

Subscribers

People subscribed via source and target branches