Merge lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-sale into lp:stock-logistic-warehouse
- 7.0-add-stock-available-sale
- Merge into 7.0
Status: | Rejected |
---|---|
Rejected by: | Pedro Manuel Baeza |
Proposed branch: | lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-sale |
Merge into: | lp:stock-logistic-warehouse |
Prerequisite: | lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available |
Diff against target: |
492 lines (+441/-0) 8 files modified
stock_available/res_config.py (+7/-0) stock_available/res_config_view.xml (+4/-0) stock_available_sale/__init__.py (+22/-0) stock_available_sale/__openerp__.py (+48/-0) stock_available_sale/product.py (+232/-0) stock_available_sale/product_view.xml (+19/-0) stock_available_sale/sale_stock.py (+43/-0) stock_available_sale/test/quoted_qty.yml (+66/-0) |
To merge this branch: | bzr merge lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-sale |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alexandre Fayolle - camptocamp | Needs Resubmitting | ||
Review via email: mp+220761@code.launchpad.net |
Commit message
Description of the change
Add stock_available
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.
The new module uses a context trick introduced in the previous branch, to make the Sale module warn salesman over insufficient quantity available to promise instead of insufficient virtual stock, with a very low code impact.
Module Co-authored by Loïc Bellier and your humble servant.
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : | # |
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote : | # |
Migrated to https:/
Unmerged revisions
- 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
1 | === modified file 'stock_available/res_config.py' | |||
2 | --- stock_available/res_config.py 2014-05-23 07:53:49 +0000 | |||
3 | +++ stock_available/res_config.py 2014-05-23 07:53:49 +0000 | |||
4 | @@ -31,4 +31,11 @@ | |||
5 | 31 | help="This will subtract incoming quantities from the quantities" | 31 | help="This will subtract incoming quantities from the quantities" |
6 | 32 | "available to promise.\n" | 32 | "available to promise.\n" |
7 | 33 | "This installs the module stock_available_immediately."), | 33 | "This installs the module stock_available_immediately."), |
8 | 34 | 'module_stock_available_sale': fields.boolean( | ||
9 | 35 | 'Exclude goods already in sale quotations', | ||
10 | 36 | help="This will subtract quantities from the sale quotations from" | ||
11 | 37 | "the quantities available to promise.\n" | ||
12 | 38 | "This installs the modules stock_available_sale.\n" | ||
13 | 39 | "If the modules sale and sale_delivery_date are not " | ||
14 | 40 | "installed, this will install them too"), | ||
15 | 34 | } | 41 | } |
16 | 35 | 42 | ||
17 | === modified file 'stock_available/res_config_view.xml' | |||
18 | --- stock_available/res_config_view.xml 2014-05-23 07:53:49 +0000 | |||
19 | +++ stock_available/res_config_view.xml 2014-05-23 07:53:49 +0000 | |||
20 | @@ -15,6 +15,10 @@ | |||
21 | 15 | <field name="module_stock_available_immediately" class="oe_inline" /> | 15 | <field name="module_stock_available_immediately" class="oe_inline" /> |
22 | 16 | <label for="module_stock_available_immediately" /> | 16 | <label for="module_stock_available_immediately" /> |
23 | 17 | </div> | 17 | </div> |
24 | 18 | <div> | ||
25 | 19 | <field name="module_stock_available_sale" class="oe_inline" /> | ||
26 | 20 | <label for="module_stock_available_sale" /> | ||
27 | 21 | </div> | ||
28 | 18 | </div> | 22 | </div> |
29 | 19 | </group> | 23 | </group> |
30 | 20 | </xpath> | 24 | </xpath> |
31 | 21 | 25 | ||
32 | === added directory 'stock_available_sale' | |||
33 | === added file 'stock_available_sale/__init__.py' | |||
34 | --- stock_available_sale/__init__.py 1970-01-01 00:00:00 +0000 | |||
35 | +++ stock_available_sale/__init__.py 2014-05-23 07:53:49 +0000 | |||
36 | @@ -0,0 +1,22 @@ | |||
37 | 1 | # -*- coding: utf-8 -*- | ||
38 | 2 | ############################################################################## | ||
39 | 3 | # | ||
40 | 4 | # This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. | ||
41 | 5 | # | ||
42 | 6 | # This program is free software: you can redistribute it and/or modify | ||
43 | 7 | # it under the terms of the GNU Affero General Public License as | ||
44 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
45 | 9 | # License, or (at your option) any later version. | ||
46 | 10 | # | ||
47 | 11 | # This program is distributed in the hope that it will be useful, | ||
48 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
49 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
50 | 14 | # GNU Affero General Public License for more details. | ||
51 | 15 | # | ||
52 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
53 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
54 | 18 | # | ||
55 | 19 | ############################################################################## | ||
56 | 20 | |||
57 | 21 | from . import product | ||
58 | 22 | from . import sale_stock | ||
59 | 0 | 23 | ||
60 | === added file 'stock_available_sale/__openerp__.py' | |||
61 | --- stock_available_sale/__openerp__.py 1970-01-01 00:00:00 +0000 | |||
62 | +++ stock_available_sale/__openerp__.py 2014-05-23 07:53:49 +0000 | |||
63 | @@ -0,0 +1,48 @@ | |||
64 | 1 | # -*- coding: utf-8 -*- | ||
65 | 2 | ############################################################################## | ||
66 | 3 | # | ||
67 | 4 | # This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. | ||
68 | 5 | # | ||
69 | 6 | # This program is free software: you can redistribute it and/or modify | ||
70 | 7 | # it under the terms of the GNU Affero General Public License as | ||
71 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
72 | 9 | # License, or (at your option) any later version. | ||
73 | 10 | # | ||
74 | 11 | # This program is distributed in the hope that it will be useful, | ||
75 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
76 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
77 | 14 | # GNU Affero General Public License for more details. | ||
78 | 15 | # | ||
79 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
80 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
81 | 18 | # | ||
82 | 19 | ############################################################################## | ||
83 | 20 | |||
84 | 21 | { | ||
85 | 22 | 'name': 'Quotations in quantity available to promise', | ||
86 | 23 | 'version': '2.0', | ||
87 | 24 | 'author': u'Numérigraphe SÀRL', | ||
88 | 25 | 'category': 'Hidden', | ||
89 | 26 | 'depends': [ | ||
90 | 27 | 'stock_available', | ||
91 | 28 | 'sale_order_dates', | ||
92 | 29 | 'sale_stock', | ||
93 | 30 | ], | ||
94 | 31 | 'description': """ | ||
95 | 32 | This module computes the quoted quantity of the Products, and subtracts it from | ||
96 | 33 | the quantities available to promise . | ||
97 | 34 | |||
98 | 35 | "Quoted" is defined as the sum of the quantities of this product in Quotations, | ||
99 | 36 | taking the context's shop or warehouse into account. | ||
100 | 37 | |||
101 | 38 | When entering sale orders, the salesperson get warned if the quantity available | ||
102 | 39 | to promise is insufficient (instead when the virtual stock is | ||
103 | 40 | insufficient).""", | ||
104 | 41 | 'data': [ | ||
105 | 42 | 'product_view.xml', | ||
106 | 43 | ], | ||
107 | 44 | 'test': [ | ||
108 | 45 | 'test/quoted_qty.yml', | ||
109 | 46 | ], | ||
110 | 47 | 'license': 'AGPL-3', | ||
111 | 48 | } | ||
112 | 0 | 49 | ||
113 | === added file 'stock_available_sale/product.py' | |||
114 | --- stock_available_sale/product.py 1970-01-01 00:00:00 +0000 | |||
115 | +++ stock_available_sale/product.py 2014-05-23 07:53:49 +0000 | |||
116 | @@ -0,0 +1,232 @@ | |||
117 | 1 | # -*- coding: utf-8 -*- | ||
118 | 2 | ############################################################################## | ||
119 | 3 | # | ||
120 | 4 | # This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. | ||
121 | 5 | # | ||
122 | 6 | # This program is free software: you can redistribute it and/or modify | ||
123 | 7 | # it under the terms of the GNU Affero General Public License as | ||
124 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
125 | 9 | # License, or (at your option) any later version. | ||
126 | 10 | # | ||
127 | 11 | # This program is distributed in the hope that it will be useful, | ||
128 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
129 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
130 | 14 | # GNU Affero General Public License for more details. | ||
131 | 15 | # | ||
132 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
133 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
134 | 18 | # | ||
135 | 19 | ############################################################################## | ||
136 | 20 | |||
137 | 21 | from openerp.osv import orm, fields | ||
138 | 22 | import decimal_precision as dp | ||
139 | 23 | |||
140 | 24 | # Function which uses the pool to call the method from the other modules too. | ||
141 | 25 | from openerp.addons.stock_available import _product_available_fnct | ||
142 | 26 | |||
143 | 27 | |||
144 | 28 | class ProductProduct(orm.Model): | ||
145 | 29 | """Add the computation for the stock available to promise""" | ||
146 | 30 | _inherit = 'product.product' | ||
147 | 31 | |||
148 | 32 | def _product_available(self, cr, uid, ids, field_names=None, arg=False, | ||
149 | 33 | context=None): | ||
150 | 34 | """Compute the quantities in Quotations.""" | ||
151 | 35 | # Check the context | ||
152 | 36 | if context is None: | ||
153 | 37 | context = {} | ||
154 | 38 | # Prepare an alternative context without 'uom', to avoid cross-category | ||
155 | 39 | # conversions when reading the available stock of components | ||
156 | 40 | if 'uom' in context: | ||
157 | 41 | context_wo_uom = context.copy() | ||
158 | 42 | del context_wo_uom['uom'] | ||
159 | 43 | else: | ||
160 | 44 | context_wo_uom = context | ||
161 | 45 | |||
162 | 46 | if field_names is None: | ||
163 | 47 | field_names = [] | ||
164 | 48 | |||
165 | 49 | # Compute the core quantities | ||
166 | 50 | res = super(ProductProduct, self)._product_available( | ||
167 | 51 | cr, uid, ids, field_names=field_names, arg=arg, context=context) | ||
168 | 52 | |||
169 | 53 | # Compute the quantities quoted/available to promise | ||
170 | 54 | if ('quoted_qty' in field_names | ||
171 | 55 | or 'immediately_usable_qty' in field_names): | ||
172 | 56 | date_str, date_args = self._get_dates(cr, uid, ids, | ||
173 | 57 | context=context) | ||
174 | 58 | |||
175 | 59 | # Limit the search to some shops according to the context | ||
176 | 60 | shop_str, shop_args = self._get_shops(cr, uid, ids, | ||
177 | 61 | context=context) | ||
178 | 62 | |||
179 | 63 | # Query the total by Product and UoM | ||
180 | 64 | cr.execute( | ||
181 | 65 | """ | ||
182 | 66 | SELECT sum(product_uom_qty), product_id, product_uom | ||
183 | 67 | FROM sale_order_line | ||
184 | 68 | INNER JOIN sale_order | ||
185 | 69 | ON (sale_order_line.order_id = sale_order.id) | ||
186 | 70 | WHERE product_id in %s | ||
187 | 71 | AND sale_order_line.state = 'draft' """ | ||
188 | 72 | + date_str + shop_str + | ||
189 | 73 | "GROUP BY sale_order_line.product_id, product_uom", | ||
190 | 74 | (tuple(ids),) + date_args + shop_args) | ||
191 | 75 | results = cr.fetchall() | ||
192 | 76 | |||
193 | 77 | # Get the UoM resources we'll need for conversion | ||
194 | 78 | # UoMs from the products | ||
195 | 79 | uoms_o = {} | ||
196 | 80 | product2uom = {} | ||
197 | 81 | for product in self.browse(cr, uid, ids, context=context): | ||
198 | 82 | product2uom[product.id] = product.uom_id | ||
199 | 83 | uoms_o[product.uom_id.id] = product.uom_id | ||
200 | 84 | # UoM from the results and the context | ||
201 | 85 | uom_obj = self.pool['product.uom'] | ||
202 | 86 | uoms = map(lambda stock_product_uom_qty: stock_product_uom_qty[2], | ||
203 | 87 | results) | ||
204 | 88 | if context.get('uom', False): | ||
205 | 89 | uoms.append(context['uom']) | ||
206 | 90 | uoms = filter(lambda stock_product_uom_qty: | ||
207 | 91 | stock_product_uom_qty not in uoms_o.keys(), uoms) | ||
208 | 92 | if uoms: | ||
209 | 93 | uoms = uom_obj.browse(cr, uid, list(set(uoms)), | ||
210 | 94 | context=context) | ||
211 | 95 | for o in uoms: | ||
212 | 96 | uoms_o[o.id] = o | ||
213 | 97 | |||
214 | 98 | # Compute the quoted quantity | ||
215 | 99 | for (amount, prod_id, prod_uom) in results: | ||
216 | 100 | # Convert the amount to the product's UoM without rounding | ||
217 | 101 | amount = amount / uoms_o[prod_uom].factor | ||
218 | 102 | if ('quoted_qty' in field_names): | ||
219 | 103 | res[prod_id]['quoted_qty'] -= amount | ||
220 | 104 | if ('immediately_usable_qty' in field_names): | ||
221 | 105 | res[prod_id]['immediately_usable_qty'] -= amount | ||
222 | 106 | |||
223 | 107 | # Round and optionally convert the results to the requested UoM | ||
224 | 108 | for prod_id, stock_qty in res.iteritems(): | ||
225 | 109 | if context.get('uom', False): | ||
226 | 110 | # Convert to the requested UoM | ||
227 | 111 | res_uom = uoms_o[context['uom']] | ||
228 | 112 | else: | ||
229 | 113 | # The conversion is unneeded but we do need the rounding | ||
230 | 114 | res_uom = product2uom[prod_id] | ||
231 | 115 | if ('quoted_qty' in field_names): | ||
232 | 116 | stock_qty['quoted_qty'] = uom_obj._compute_qty_obj( | ||
233 | 117 | cr, uid, product2uom[prod_id], | ||
234 | 118 | stock_qty['quoted_qty'], | ||
235 | 119 | res_uom) | ||
236 | 120 | if ('immediately_usable_qty' in field_names): | ||
237 | 121 | stock_qty['immediately_usable_qty'] = \ | ||
238 | 122 | uom_obj._compute_qty_obj( | ||
239 | 123 | cr, uid, product2uom[prod_id], | ||
240 | 124 | stock_qty['immediately_usable_qty'], | ||
241 | 125 | res_uom) | ||
242 | 126 | return self._update_virtual_available(cr, uid, res, context=context) | ||
243 | 127 | |||
244 | 128 | def _get_shops(self, cr, uid, ids, context=None): | ||
245 | 129 | """Find the shops matching the current context | ||
246 | 130 | |||
247 | 131 | See the helptext for the field quoted_qty for details""" | ||
248 | 132 | shop_ids = [] | ||
249 | 133 | # Account for one or several locations in the context | ||
250 | 134 | # Take any shop using any warehouse that has these locations as stock | ||
251 | 135 | # location | ||
252 | 136 | if context.get('location', False): | ||
253 | 137 | # Either a single or multiple locations can be in the context | ||
254 | 138 | if not isinstance(context['location'], list): | ||
255 | 139 | location_ids = [context['location']] | ||
256 | 140 | else: | ||
257 | 141 | location_ids = context['location'] | ||
258 | 142 | # Add the children locations | ||
259 | 143 | if context.get('compute_child', True): | ||
260 | 144 | child_location_ids = self.pool['stock.location'].search( | ||
261 | 145 | cr, uid, [('location_id', 'child_of', location_ids)]) | ||
262 | 146 | location_ids = child_location_ids or location_ids | ||
263 | 147 | # Get the corresponding Shops | ||
264 | 148 | cr.execute( | ||
265 | 149 | """ | ||
266 | 150 | SELECT id FROM sale_shop | ||
267 | 151 | WHERE warehouse_id IN ( | ||
268 | 152 | SELECT id | ||
269 | 153 | FROM stock_warehouse | ||
270 | 154 | WHERE lot_stock_id IN %s)""", | ||
271 | 155 | (tuple(location_ids),)) | ||
272 | 156 | res_location = cr.fetchone() | ||
273 | 157 | if res_location: | ||
274 | 158 | shop_ids.append(res_location) | ||
275 | 159 | |||
276 | 160 | # Account for a warehouse in the context | ||
277 | 161 | # Take any draft order in any shop using this warehouse | ||
278 | 162 | if context.get('warehouse', False): | ||
279 | 163 | cr.execute("SELECT id " | ||
280 | 164 | "FROM sale_shop " | ||
281 | 165 | "WHERE warehouse_id = %s", | ||
282 | 166 | (int(context['warehouse']),)) | ||
283 | 167 | res_wh = cr.fetchone() | ||
284 | 168 | if res_wh: | ||
285 | 169 | shop_ids.append(res_wh) | ||
286 | 170 | |||
287 | 171 | # If we are in a single Shop context, only count the quotations from | ||
288 | 172 | # this shop | ||
289 | 173 | if context.get('shop', False): | ||
290 | 174 | shop_ids.append(context['shop']) | ||
291 | 175 | # Build the SQL to restrict to the selected shops | ||
292 | 176 | shop_str = '' | ||
293 | 177 | if shop_ids: | ||
294 | 178 | shop_str = 'AND sale_order.shop_id IN %s' | ||
295 | 179 | |||
296 | 180 | if shop_ids: | ||
297 | 181 | shop_ids = (tuple(shop_ids),) | ||
298 | 182 | else: | ||
299 | 183 | shop_ids = () | ||
300 | 184 | return shop_str, shop_ids | ||
301 | 185 | |||
302 | 186 | def _get_dates(self, cr, uid, ids, context=None): | ||
303 | 187 | """Build SQL criteria to match the context's from/to dates""" | ||
304 | 188 | # If we are in a context with dates, only consider the quotations to be | ||
305 | 189 | # delivered at these dates. | ||
306 | 190 | # If no delivery date was entered, use the order date instead | ||
307 | 191 | if not context: | ||
308 | 192 | return '', () | ||
309 | 193 | |||
310 | 194 | from_date = context.get('from_date', False) | ||
311 | 195 | to_date = context.get('to_date', False) | ||
312 | 196 | date_str = '' | ||
313 | 197 | date_args = [] | ||
314 | 198 | if from_date: | ||
315 | 199 | date_str = """AND COALESCE( | ||
316 | 200 | sale_order.requested_date, | ||
317 | 201 | sale_order.date_order) >= %s """ | ||
318 | 202 | date_args.append(from_date) | ||
319 | 203 | if to_date: | ||
320 | 204 | date_str += """AND COALESCE( | ||
321 | 205 | sale_order.requested_date, | ||
322 | 206 | sale_order.date_order) <= %s """ | ||
323 | 207 | date_args.append(to_date) | ||
324 | 208 | |||
325 | 209 | if date_args: | ||
326 | 210 | date_args = (tuple(date_args),) | ||
327 | 211 | else: | ||
328 | 212 | date_args = () | ||
329 | 213 | return date_str, date_args | ||
330 | 214 | |||
331 | 215 | _columns = { | ||
332 | 216 | 'quoted_qty': fields.function( | ||
333 | 217 | _product_available_fnct, method=True, multi='qty_available', | ||
334 | 218 | type='float', | ||
335 | 219 | digits_compute=dp.get_precision('Product Unit of Measure'), | ||
336 | 220 | string='Quoted', | ||
337 | 221 | help="Total quantity of this Product that have been included in " | ||
338 | 222 | "Quotations (Draft Sale Orders).\n" | ||
339 | 223 | "In a context with a single Shop, this includes the " | ||
340 | 224 | "Quotation processed at this Shop.\n" | ||
341 | 225 | "In a context with a single Warehouse, this includes " | ||
342 | 226 | "Quotation processed in any Shop using this Warehouse.\n" | ||
343 | 227 | "In a context with a single Stock Location, this includes " | ||
344 | 228 | "Quotation processed at any shop using any Warehouse using " | ||
345 | 229 | "this Location, or any of its children, as it's Stock " | ||
346 | 230 | "Location.\n" | ||
347 | 231 | "Otherwise, this includes every Quotation."), | ||
348 | 232 | } | ||
349 | 0 | 233 | ||
350 | === added file 'stock_available_sale/product_view.xml' | |||
351 | --- stock_available_sale/product_view.xml 1970-01-01 00:00:00 +0000 | |||
352 | +++ stock_available_sale/product_view.xml 2014-05-23 07:53:49 +0000 | |||
353 | @@ -0,0 +1,19 @@ | |||
354 | 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
355 | 2 | <openerp> | ||
356 | 3 | <data> | ||
357 | 4 | <!-- Add the quantity available to promise in the product form --> | ||
358 | 5 | <record id="view_product_form_quoted_qty" model="ir.ui.view"> | ||
359 | 6 | <field name="name">product.form.quoted_qty</field> | ||
360 | 7 | <field name="model">product.product</field> | ||
361 | 8 | <field name="type">form</field> | ||
362 | 9 | <field name="inherit_id" ref="stock_available.view_stock_available_form" /> | ||
363 | 10 | <field name="arch" type="xml"> | ||
364 | 11 | <data> | ||
365 | 12 | <xpath expr="//field[@name='immediately_usable_qty']" position="after"> | ||
366 | 13 | <field name="quoted_qty"/> | ||
367 | 14 | </xpath> | ||
368 | 15 | </data> | ||
369 | 16 | </field> | ||
370 | 17 | </record> | ||
371 | 18 | </data> | ||
372 | 19 | </openerp> | ||
373 | 0 | 20 | ||
374 | === added file 'stock_available_sale/sale_stock.py' | |||
375 | --- stock_available_sale/sale_stock.py 1970-01-01 00:00:00 +0000 | |||
376 | +++ stock_available_sale/sale_stock.py 2014-05-23 07:53:49 +0000 | |||
377 | @@ -0,0 +1,43 @@ | |||
378 | 1 | # -*- coding: utf-8 -*- | ||
379 | 2 | ############################################################################## | ||
380 | 3 | # | ||
381 | 4 | # This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. | ||
382 | 5 | # | ||
383 | 6 | # This program is free software: you can redistribute it and/or modify | ||
384 | 7 | # it under the terms of the GNU Affero General Public License as | ||
385 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
386 | 9 | # License, or (at your option) any later version. | ||
387 | 10 | # | ||
388 | 11 | # This program is distributed in the hope that it will be useful, | ||
389 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
390 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
391 | 14 | # GNU Affero General Public License for more details. | ||
392 | 15 | # | ||
393 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
394 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
395 | 18 | # | ||
396 | 19 | ############################################################################## | ||
397 | 20 | |||
398 | 21 | from openerp.osv import osv | ||
399 | 22 | |||
400 | 23 | |||
401 | 24 | class sale_order_line(osv.osv): | ||
402 | 25 | _inherit = "sale.order.line" | ||
403 | 26 | |||
404 | 27 | def product_id_change(self, cr, uid, ids, pricelist, product, qty=0, | ||
405 | 28 | uom=False, qty_uos=0, uos=False, name='', | ||
406 | 29 | partner_id=False, lang=False, update_tax=True, | ||
407 | 30 | date_order=False, packaging=False, | ||
408 | 31 | fiscal_position=False, flag=False, context=None): | ||
409 | 32 | """Base the stock checking on the quantity available to promise | ||
410 | 33 | |||
411 | 34 | This is done by tweaking the context, to keep the impact minimum""" | ||
412 | 35 | if context is None: | ||
413 | 36 | context = {} | ||
414 | 37 | context = dict(context, virtual_is_immediately_usable=True) | ||
415 | 38 | return super(sale_order_line, self).product_id_change( | ||
416 | 39 | cr, uid, ids, pricelist, product, qty=qty, | ||
417 | 40 | uom=uom, qty_uos=qty_uos, uos=uos, name=name, | ||
418 | 41 | partner_id=partner_id, lang=lang, update_tax=update_tax, | ||
419 | 42 | date_order=date_order, packaging=packaging, | ||
420 | 43 | fiscal_position=fiscal_position, flag=flag, context=context) | ||
421 | 0 | 44 | ||
422 | === added directory 'stock_available_sale/test' | |||
423 | === added file 'stock_available_sale/test/quoted_qty.yml' | |||
424 | --- stock_available_sale/test/quoted_qty.yml 1970-01-01 00:00:00 +0000 | |||
425 | +++ stock_available_sale/test/quoted_qty.yml 2014-05-23 07:53:49 +0000 | |||
426 | @@ -0,0 +1,66 @@ | |||
427 | 1 | - Test the computation of the quoted quantity on product.product_product_10 | ||
428 | 2 | |||
429 | 3 | - Create a UoM in the category of PCE | ||
430 | 4 | - !record {model: product.uom, id: thousand}: | ||
431 | 5 | name: Thousand | ||
432 | 6 | factor: 0.001 | ||
433 | 7 | rounding: 0.00 | ||
434 | 8 | uom_type: bigger | ||
435 | 9 | category_id: product.product_uom_categ_unit | ||
436 | 10 | |||
437 | 11 | - Cancel all the previous Quotations | ||
438 | 12 | - !python {model: sale.order}: | | ||
439 | 13 | line_ids = self.pool['sale.order.line'].search( | ||
440 | 14 | cr, uid, [('product_id', '=', ref('product.product_product_10')), | ||
441 | 15 | ('state', '=', 'draft')]) | ||
442 | 16 | ids = [l.order_id.id for l in self.pool['sale.order.line'].browse(cr, uid, line_ids)] | ||
443 | 17 | if ids: | ||
444 | 18 | self.action_cancel(cr, uid, ids) | ||
445 | 19 | - The quoted quantity should be 0 | ||
446 | 20 | - !assert {model: product.product, id: product.product_product_10, string: "Check quoted_qty"}: | ||
447 | 21 | - quoted_qty == 0.0 | ||
448 | 22 | |||
449 | 23 | - Enter a Quotation | ||
450 | 24 | - !record {model: sale.order, id: order1}: | ||
451 | 25 | order_line: | ||
452 | 26 | - name: Quotation 1 | ||
453 | 27 | product_uom: product.product_uom_unit | ||
454 | 28 | product_uom_qty: 107.0 | ||
455 | 29 | state: draft | ||
456 | 30 | product_id: product.product_product_10 | ||
457 | 31 | partner_id: base.res_partner_2 | ||
458 | 32 | partner_invoice_id: base.res_partner_address_8 | ||
459 | 33 | partner_shipping_id: base.res_partner_address_8 | ||
460 | 34 | pricelist_id: product.list0 | ||
461 | 35 | - The quoted qty should match the single quotation | ||
462 | 36 | - !assert {model: product.product, id: product.product_product_10, string: "Check quoted_qty"}: | ||
463 | 37 | - quoted_qty == -107.0 | ||
464 | 38 | |||
465 | 39 | - Enter another Quotation | ||
466 | 40 | - !record {model: sale.order, id: order2}: | ||
467 | 41 | order_line: | ||
468 | 42 | - name: Quotation 1 | ||
469 | 43 | product_uom: thousand | ||
470 | 44 | product_uom_qty: 0.613 | ||
471 | 45 | state: draft | ||
472 | 46 | product_id: product.product_product_10 | ||
473 | 47 | partner_id: base.res_partner_2 | ||
474 | 48 | partner_invoice_id: base.res_partner_address_9 | ||
475 | 49 | partner_shipping_id: base.res_partner_address_9 | ||
476 | 50 | pricelist_id: product.list0 | ||
477 | 51 | - The quoted qty should match the total of the quotations | ||
478 | 52 | - !assert {model: product.product, id: product.product_product_10, string: "Check quoted quantity"}: | ||
479 | 53 | - quoted_qty == -720.0 | ||
480 | 54 | - Use the context to report in another UoM | ||
481 | 55 | - !assert {model: product.product, id: product.product_product_10, string: "Check in other UoM", context: "{'uom': ref('thousand')}"}: | ||
482 | 56 | - quoted_qty == -0.72 | ||
483 | 57 | - Use the context to report in the default UoM | ||
484 | 58 | - !assert {model: product.product, id: product.product_product_10, string: "Check in False UoM", context: "{'uom': False}"}: | ||
485 | 59 | - quoted_qty == -720.0 | ||
486 | 60 | |||
487 | 61 | - Confirm one of the Quotations | ||
488 | 62 | - !workflow {model: sale.order, action: order_confirm, ref: order1} | ||
489 | 63 | - The quoted qty should match the remaining quotation | ||
490 | 64 | - !assert {model: product.product, id: product.product_product_10, string: "Check quoted quantity"}: | ||
491 | 65 | - quoted_qty == -613.0 | ||
492 | 66 |
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?