[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:
commit
2f5f6c831b
|
@ -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'),
|
||||
|
|
|
@ -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<0;blue:virtual_available>=0 and state in ('draft', 'end', 'obsolete');black:virtual_available>=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"/>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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','>',0)]"/>
|
||||
<filter name="virtual_stock_available" string="Forecast Available Products" domain="[('virtual_available','>',0)]"/>
|
||||
<filter name="real_stock_negative" string="Exhausted Stock" domain="[('qty_available','<=',0)]"/>
|
||||
<filter name="virtual_stock_negative" string="Forecast Exhausted Stock" domain="[('virtual_available','<=',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<0, 'blue':virtual_available>=0 and state in ('draft', 'end', 'obsolete'), 'black':virtual_available>=0 and state not in ('draft', 'end', 'obsolete')}</attribute>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
Loading…
Reference in New Issue