diff --git a/addons/product/product.py b/addons/product/product.py index f22fcdbdfbf..e093cd0c0a7 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -508,16 +508,6 @@ class product_product(osv.osv): res.setdefault(id, 0.0) return res - def _get_product_available_func(states, what): - def _product_available(self, cr, uid, ids, name, arg, context=None): - return {}.fromkeys(ids, 0.0) - return _product_available - - _product_qty_available = _get_product_available_func(('done',), ('in', 'out')) - _product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out')) - _product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',)) - _product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',)) - def _product_lst_price(self, cr, uid, ids, name, arg, context=None): res = {} product_uom_obj = self.pool.get('product.uom') @@ -622,10 +612,6 @@ class product_product(osv.osv): _inherit = ['mail.thread'] _order = 'default_code,name_template' _columns = { - 'qty_available': fields.function(_product_qty_available, type='float', string='Quantity On Hand'), - 'virtual_available': fields.function(_product_virtual_available, type='float', string='Quantity Available'), - 'incoming_qty': fields.function(_product_incoming_qty, type='float', string='Incoming'), - 'outgoing_qty': fields.function(_product_outgoing_qty, type='float', string='Outgoing'), 'price': fields.function(_product_price, type='float', string='Price', digits_compute=dp.get_precision('Product Price')), 'lst_price': fields.function(_product_lst_price, type='float', string='Public Price', digits_compute=dp.get_precision('Product Price')), 'code': fields.function(_product_code, type='char', string='Internal Reference'), diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 99820f8b02d..4a842a0c791 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -33,14 +33,12 @@ product.product - + - - diff --git a/addons/stock/product.py b/addons/stock/product.py index 6ca53a5ad0d..f7fde1c33a5 100644 --- a/addons/stock/product.py +++ b/addons/stock/product.py @@ -21,6 +21,7 @@ from openerp.osv import fields, osv from openerp.tools.translate import _ +from openerp.tools.safe_eval import safe_eval as eval import openerp.addons.decimal_precision as dp class product_product(osv.osv): @@ -99,13 +100,13 @@ class product_product(osv.osv): ) def _get_domain_dates(self, cr, uid, ids, context): - from_date = context.get('from_date',False) - to_date = context.get('to_date',False) + from_date = context.get('from_date', False) + to_date = context.get('to_date', False) domain = [] if from_date: - domain.append(('date','>=',from_date)) + domain.append(('date', '>=', from_date)) if to_date: - domain.append(('date','<=',to_date)) + domain.append(('date', '<=', to_date)) return domain def _product_available(self, cr, uid, ids, field_names=None, arg=False, context=None): @@ -114,26 +115,25 @@ class product_product(osv.osv): domain_products = [('product_id', 'in', ids)] domain_quant, domain_move_in, domain_move_out = self._get_domain_locations(cr, uid, ids, context=context) - domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state','not in',('done','cancel'))] + domain_products - domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state','not in',('done','cancel'))] + domain_products + domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel'))] + domain_products + domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel'))] + domain_products domain_quant += domain_products if context.get('lot_id') or context.get('owner_id') or context.get('package_id'): if context.get('lot_id'): - domain_quant.append(('lot_id','=',context['lot_id'])) + domain_quant.append(('lot_id', '=', context['lot_id'])) if context.get('owner_id'): - domain_quant.append(('owner_id','=',context['owner_id'])) + domain_quant.append(('owner_id', '=', context['owner_id'])) if context.get('package_id'): - domain_quant.append(('package_id','=',context['package_id'])) - moves_in = [] + domain_quant.append(('package_id', '=', context['package_id'])) + moves_in = [] moves_out = [] else: -# if field_names in ['incoming_qty', 'outgoing_qty', 'virtual_available']: - moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context) + moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context) moves_out = self.pool.get('stock.move').read_group(cr, uid, domain_move_out, ['product_id', 'product_qty'], ['product_id'], context=context) - + quants = self.pool.get('stock.quant').read_group(cr, uid, domain_quant, ['product_id', 'qty'], ['product_id'], context=context) quants = dict(map(lambda x: (x['product_id'][0], x['qty']), quants)) - + moves_in = dict(map(lambda x: (x['product_id'][0], x['product_qty']), moves_in)) moves_out = dict(map(lambda x: (x['product_id'][0], x['product_qty']), moves_out)) @@ -144,16 +144,39 @@ class product_product(osv.osv): 'incoming_qty': moves_in.get(id, 0.0), 'outgoing_qty': moves_out.get(id, 0.0), 'virtual_available': quants.get(id, 0.0) + moves_in.get(id, 0.0) - moves_out.get(id, 0.0), - } - + } + + return res + + def _search_product_quantity(self, cr, uid, obj, name, domain, context): + res = [] + for field, operator, value in domain: + #to prevent sql injections + assert field in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty'), 'Invalid domain left operand' + assert operator in ('<', '>', '=', '<=', '>='), 'Invalid domain operator' + assert isinstance(value, (float, int)), 'Invalid domain right operand' + + if operator == '=': + operator = '==' + + product_ids = self.search(cr, uid, [], context=context) + ids = [] + if product_ids: + #TODO: use a query instead of this browse record which is probably making the too much requests, but don't forget + #the context that can be set with a location, an owner... + for element in self.browse(cr, uid, product_ids, context=context): + if eval(str(element[field]) + operator + str(value)): + ids.append(element.id) + res.append(('id', 'in', ids)) return res _columns = { 'reception_count': fields.function(_stock_move_count, string="Reception", type='integer', multi='pickings'), 'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'), 'qty_available': fields.function(_product_available, multi='qty_available', - type='float', digits_compute=dp.get_precision('Product Unit of Measure'), + type='float', digits_compute=dp.get_precision('Product Unit of Measure'), string='Quantity On Hand', + fnct_search=_search_product_quantity, 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" @@ -165,8 +188,9 @@ class product_product(osv.osv): "Otherwise, this includes goods stored in any Stock Location " "with 'internal' type."), 'virtual_available': fields.function(_product_available, multi='qty_available', - type='float', digits_compute=dp.get_precision('Product Unit of Measure'), - string='Forecasted Quantity', + type='float', digits_compute=dp.get_precision('Product Unit of Measure'), + string='Forecast Quantity', + fnct_search=_search_product_quantity, help="Forecast quantity (computed as Quantity On Hand " "- Outgoing + Incoming)\n" "In a context with a single Stock Location, this includes " @@ -179,8 +203,9 @@ class product_product(osv.osv): "Otherwise, this includes goods stored in any Stock Location " "with 'internal' type."), 'incoming_qty': fields.function(_product_available, multi='qty_available', - type='float', digits_compute=dp.get_precision('Product Unit of Measure'), + type='float', digits_compute=dp.get_precision('Product Unit of Measure'), string='Incoming', + fnct_search=_search_product_quantity, 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" @@ -193,8 +218,9 @@ class product_product(osv.osv): "Otherwise, this includes goods arriving to any Stock " "Location with 'internal' type."), 'outgoing_qty': fields.function(_product_available, multi='qty_available', - type='float', digits_compute=dp.get_precision('Product Unit of Measure'), + type='float', digits_compute=dp.get_precision('Product Unit of Measure'), string='Outgoing', + fnct_search=_search_product_quantity, help="Quantity of products that are planned to leave.\n" "In a context with a single Stock Location, this includes " "goods leaving this Location, or any of its children.\n" diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml index 277ec5b40e5..76ae15475ce 100644 --- a/addons/stock/product_view.xml +++ b/addons/stock/product_view.xml @@ -11,6 +11,30 @@ + + + + + + + + + + + + + product.stock.tree.inherit + product.product + + + + + + + + + {'red':virtual_available<0, 'blue':virtual_available>=0 and state in ('draft', 'end', 'obsolete'), 'black':virtual_available>=0 and state not in ('draft', 'end', 'obsolete')} +