[MERGE] stock: real and virtual quantities of product stored and made sortable/searchable

lp bug: https://launchpad.net/bugs/1268983 fixed

bzr revid: qdp-launchpad@openerp.com-20140124150704-eic48otp9cp2ncni
This commit is contained in:
Quentin (OpenERP) 2014-01-24 16:07:04 +01:00
commit 2f5f6c831b
4 changed files with 72 additions and 38 deletions

View File

@ -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'),

View File

@ -33,14 +33,12 @@
<field name="model">product.product</field>
<field eval="7" name="priority"/>
<field name="arch" type="xml">
<tree colors="red:virtual_available&lt;0;blue:virtual_available&gt;=0 and state in ('draft', 'end', 'obsolete');black:virtual_available&gt;=0 and state not in ('draft', 'end', 'obsolete')" string="Products">
<tree string="Products">
<field name="default_code"/>
<field name="name"/>
<field name="categ_id" invisible="1"/>
<field name="type" invisible="1"/>
<field name="uom_id" string="Unit of Measure" groups="product.group_uom"/>
<field name="qty_available"/>
<field name="virtual_available"/>
<field name="lst_price"/>
<field name="price" invisible="not context.get('pricelist',False)"/>
<field name="standard_price" invisible="1"/>

View File

@ -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"

View File

@ -11,6 +11,30 @@
<field name="location_id" widget="selection" context="{'location': self}"/>
<field name="warehouse_id" widget="selection" context="{'warehouse': self}"/>
</field>
<field name="categ_id" position="before">
<separator/>
<filter name="real_stock_available" string="Available Products" domain="[('qty_available','&gt;',0)]"/>
<filter name="virtual_stock_available" string="Forecast Available Products" domain="[('virtual_available','&gt;',0)]"/>
<filter name="real_stock_negative" string="Exhausted Stock" domain="[('qty_available','&lt;=',0)]"/>
<filter name="virtual_stock_negative" string="Forecast Exhausted Stock" domain="[('virtual_available','&lt;=',0)]"/>
</field>
</field>
</record>
<record id="view_stock_product_tree" model="ir.ui.view">
<field name="name">product.stock.tree.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view"/>
<field name="arch" type="xml">
<field name="uom_id" position="after">
<field name="qty_available"/>
<field name="virtual_available"/>
</field>
<tree position="attributes">
<attribute name="colors">{'red':virtual_available&lt;0, 'blue':virtual_available&gt;=0 and state in ('draft', 'end', 'obsolete'), 'black':virtual_available&gt;=0 and state not in ('draft', 'end', 'obsolete')}</attribute>
</tree>
</field>
</record>