[MERGE] purchase: refactor onchange_product_id, stock: add test case

bzr revid: rco@openerp.com-20120124130921-g887vnw9fudt5zxo
This commit is contained in:
Raphael Collet 2012-01-24 14:09:21 +01:00
commit 4392641861
5 changed files with 123 additions and 98 deletions

View File

@ -697,100 +697,97 @@ class purchase_order_line(osv.osv):
default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
#TOFIX:
# - name of method should "onchange_product_id"
# - docstring
# - merge 'product_uom_change' method
# - split into small internal methods for clearity
def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
partner_id, date_order=False, fiscal_position=False, date_planned=False,
name=False, price_unit=False, notes=False, context={}):
if not pricelist:
def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
name=False, price_unit=False, notes=False, context=None):
"""
onchange handler of product_uom.
"""
if not uom_id:
return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or'', 'product_uom' : uom_id or False}}
return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
name=name, price_unit=price_unit, notes=notes, context=context)
def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
name=False, price_unit=False, notes=False, context=None):
"""
onchange handler of product_id.
"""
if context is None:
context = {}
res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or '', 'product_uom' : uom_id or False}}
if not product_id:
return res
product_product = self.pool.get('product.product')
product_uom = self.pool.get('product.uom')
res_partner = self.pool.get('res.partner')
product_supplierinfo = self.pool.get('product.supplierinfo')
product_pricelist = self.pool.get('product.pricelist')
account_fiscal_position = self.pool.get('account.fiscal.position')
account_tax = self.pool.get('account.tax')
# - check for the presence of partner_id and pricelist_id
if not pricelist_id:
raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist or a supplier in the purchase form !\nPlease set one before choosing a product.'))
if not partner_id:
if not partner_id:
raise osv.except_osv(_('No Partner!'), _('You have to select a partner in the purchase form !\nPlease set one partner before choosing a product.'))
if not product:
return {'value': {'price_unit': price_unit or 0.0, 'name': name or '',
'notes': notes or'', 'product_uom' : uom or False}, 'domain':{'product_uom':[]}}
res = {}
prod= self.pool.get('product.product').browse(cr, uid, product)
product_uom_pool = self.pool.get('product.uom')
lang=False
if partner_id:
lang=self.pool.get('res.partner').read(cr, uid, partner_id, ['lang'])['lang']
# - determine name and notes based on product in partner lang.
lang = res_partner.browse(cr, uid, partner_id).lang
context_partner = {'lang': lang, 'partner_id': partner_id}
product = product_product.browse(cr, uid, product_id, context=context_partner)
res['value'].update({'name': product.name, 'notes': notes or product.description_purchase})
# - set a domain on product_uom
res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
prod_uom_po = prod.uom_po_id.id
if not uom:
uom = prod_uom_po
# - check that uom and product uom belong to the same category
product_uom_po_id = product.uom_po_id.id
if not uom_id:
uom_id = product_uom_po_id
if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
res['warning'] = {'title': _('Warning'), 'message': _('Selected UOM does not belong to the same category as the product UOM')}
uom_id = product_uom_po_id
res['value'].update({'product_uom': uom_id})
# - determine product_qty and date_planned based on seller info
if not date_order:
date_order = time.strftime('%Y-%m-%d')
qty = qty or 1.0
seller_delay = 0
if uom:
uom1_cat = prod.uom_id.category_id.id
uom2_cat = product_uom_pool.browse(cr, uid, uom).category_id.id
if uom1_cat != uom2_cat:
uom = False
supplierinfo_ids = product_supplierinfo.search(cr, uid, [('name','=',partner_id),('product_id','=',product.id)])
for supplierinfo in product_supplierinfo.browse(cr, uid, supplierinfo_ids, context=context):
seller_delay = supplierinfo.delay
if supplierinfo.product_uom.id != uom_id:
res['warning'] = {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
if qty < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
res['warning'] = {'title': _('Warning'), 'message': _('The selected supplier has a minimal quantity set to %s %s, you should not purchase less.') % (supplierinfo.min_qty, supplierinfo.product_uom.name)}
qty = min_qty
prod_name = self.pool.get('product.product').name_get(cr, uid, [prod.id], context=context_partner)[0][1]
res = {}
for s in prod.seller_ids:
if s.name.id == partner_id:
seller_delay = s.delay
if s.product_uom:
temp_qty = product_uom_pool._compute_qty(cr, uid, s.product_uom.id, s.min_qty, to_uom_id=prod.uom_id.id)
uom = s.product_uom.id #prod_uom_po
temp_qty = s.min_qty # supplier _qty assigned to temp
if qty < temp_qty: # If the supplier quantity is greater than entered from user, set minimal.
qty = temp_qty
res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier has a minimal quantity set to %s, you should not purchase less.') % qty}})
qty_in_product_uom = product_uom_pool._compute_qty(cr, uid, uom, qty, to_uom_id=prod.uom_id.id)
price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
product, qty_in_product_uom or 1.0, partner_id, {
'uom': uom,
'date': date_order,
})[pricelist]
dt = (datetime.now() + relativedelta(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
dt = (datetime.strptime(date_order, '%Y-%m-%d') + relativedelta(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
res['value'].update({'date_planned': date_planned or dt, 'product_qty': qty})
# - determine price_unit and taxes_id
price = product_pricelist.price_get(cr, uid, [pricelist_id],
product.id, qty or 1.0, partner_id, {'uom': uom_id, 'date': date_order})[pricelist_id]
taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
res.update({'value': {'price_unit': price, 'name': prod_name,
'taxes_id':map(lambda x: x.id, prod.supplier_taxes_id),
'date_planned': date_planned or dt,'notes': notes or prod.description_purchase,
'product_qty': qty,
'product_uom': prod.uom_id.id}})
domain = {}
taxes = self.pool.get('account.tax').browse(cr, uid,map(lambda x: x.id, prod.supplier_taxes_id))
fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
res['value']['taxes_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
res2 = self.pool.get('product.uom').read(cr, uid, [prod.uom_id.id], ['category_id'])
res3 = prod.uom_id.category_id.id
domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
if res2[0]['category_id'][0] != res3:
raise osv.except_osv(_('Wrong Product UOM !'), _('You have to select a product UOM in the same category than the purchase UOM of the product'))
res['domain'] = domain
return res
#TOFIX:
# - merge into 'product_id_change' method
def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
partner_id, date_order=False, fiscal_position=False, date_planned=False,
name=False, price_unit=False, notes=False, context={}):
res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
partner_id, date_order=date_order, fiscal_position=fiscal_position, date_planned=date_planned,
name=name, price_unit=price_unit, notes=notes, context=context)
if 'product_uom' in res['value']:
if uom and (uom != res['value']['product_uom']) and res['value']['product_uom']:
seller_uom_name = self.pool.get('product.uom').read(cr, uid, [res['value']['product_uom']], ['name'])[0]['name']
res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % seller_uom_name }})
del res['value']['product_uom']
if not uom:
res['value']['price_unit'] = 0.0
return res
product_id_change = onchange_product_id
product_uom_change = onchange_product_uom
def action_confirm(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)

View File

@ -74,3 +74,12 @@
- product_id: product.product_product_1
product_qty: 15
-
!record {model: product.product, id: stock.product_icecream}:
uom_id: product.product_uom_gram
-
!record {model: purchase.order, id: order_purchase_icecream}:
partner_id: base.res_partner_asus
order_line:
- product_id: stock.product_icecream

View File

@ -359,9 +359,9 @@
<form string="Purchase Order Line">
<notebook colspan="4">
<page string="Order Line">
<field name="product_id" colspan="4" context="{'partner_id':parent.partner_id, 'quantity':product_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'warehouse':parent.warehouse_id}" on_change="product_id_change(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes,context)" required="1"/>
<field name="product_qty" context="{'partner_id':parent.partner_id, 'quantity':product_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'warehouse':parent.warehouse_id}" on_change="product_id_change(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes,context)"/>
<field name="product_uom" on_change="product_uom_change(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes)"/>
<field name="product_id" colspan="4" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes,context)" required="1"/>
<field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes,context)"/>
<field name="product_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes,context)"/>
<field colspan="4" name="name"/>
<field name="date_planned" widget="date"/>
<field name="price_unit"/>

View File

@ -42,8 +42,10 @@
uom_po_id: product.product_uom_kgm
procure_method: make_to_stock
property_stock_inventory: location_opening
property_stock_account_input: location_refrigerator
property_stock_account_output: location_delivery_counter
valuation: real_time
cost_method: average
property_stock_account_input: account.o_expense
property_stock_account_output: account.o_income
description: Ice cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
-

View File

@ -48,7 +48,7 @@
assert backorder, "Backorder should be created after partial shipment."
assert backorder.state == 'done', "Backorder should be close after received."
for move_line in backorder.move_lines:
assert move_line.product_qty == 40, "Qty in backorder is not correspond."
assert move_line.product_qty == 40, "Qty in backorder does not correspond."
assert move_line.state == 'done', "Move line of backorder should be closed."
-
I receive another 10kgm Ice-cream.
@ -72,7 +72,7 @@
shipment = self.browse(cr, uid, ref("incomming_shipment"))
assert shipment.state == 'done', "shipment should be close after received."
for move_line in shipment.move_lines:
assert move_line.product_qty == 10, "Qty is not correspond."
assert move_line.product_qty == 10, "Qty does not correspond."
assert move_line.state == 'done', "Move line should be closed."
-
@ -111,8 +111,8 @@
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_icecream'), context=context)
assert product.qty_available == 140, "Stock is not correspond."
assert product.virtual_available == 10, "Vitual stock is not correspond."
assert product.qty_available == 140, "Stock does not correspond."
assert product.virtual_available == 10, "Vitual stock does not correspond."
-
I split incomming shipment into lots. each lot contain 10 kgm Ice-cream.
@ -146,9 +146,26 @@
move_ids = self.search(cr, uid, [('location_dest_id','=',ref('location_refrigerator')),('prodlot_id','in',lot_ids)])
assert len(move_ids) == 4, 'move lines are not correspond per prodcution lot after splited.'
for move in self.browse(cr, uid, move_ids, context=context):
assert move.prodlot_id.name in ['incoming_lot0', 'incoming_lot1', 'incoming_lot2', 'incoming_lot3'], "lot is not correspond."
assert move.product_qty == 10, "qty is not correspond per production lot."
assert move.prodlot_id.name in ['incoming_lot0', 'incoming_lot1', 'incoming_lot2', 'incoming_lot3'], "lot does not correspond."
assert move.product_qty == 10, "qty does not correspond per production lot."
context.update({'active_model':'stock.move', 'active_id':move_ids[0],'active_ids': move_ids})
-
I check the stock valuation account entries.
-
!python {model: account.move}: |
incomming_shipment = self.pool.get('stock.picking').browse(cr, uid, ref('incomming_shipment'), context=context)
account_move_ids = self.search(cr, uid, [('ref','=',incomming_shipment.name)])
assert len(account_move_ids), "account move should be created."
account_move = self.browse(cr, uid, account_move_ids[0], context=context)
assert len(account_move.line_id) == len(incomming_shipment.move_lines) + 1, 'accuont entries are not correspond.'
for account_move_line in account_move.line_id:
for stock_move in incomming_shipment.move_lines:
if account_move_line.account_id.id == stock_move.product_id.property_stock_account_input.id:
assert account_move_line.credit == 800.0, "Credit amount does not correspond."
assert account_move_line.debit == 0.0, "Debit amount does not correspond."
else:
assert account_move_line.credit == 0.0, "Credit amount does not correspond."
assert account_move_line.debit == 800.0, "Debit amount does not correspond."
-
I consume 1 kgm ice-cream from each incoming lots into internal production.
-
@ -172,19 +189,19 @@
!python {model: stock.location}: |
ctx = {'product_id': ref('product_icecream')}
refrigerator_location = self.browse(cr, uid, ref('location_refrigerator'), context=ctx)
assert refrigerator_location.stock_real == 131.96, 'stock is not correspond in refrigerator location.'
assert refrigerator_location.stock_real == 131.96, 'stock does not correspond in refrigerator location.'
scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
assert scrapped_location.stock_real == 0.010*4, 'scraped stock is not correspond in scrap location.'
assert scrapped_location.stock_real == 0.010*4, 'scraped stock does not correspond in scrap location.'
production_location = self.browse(cr, uid, ref('location_production'), context=ctx)
assert production_location.stock_real == 1*4, 'consume stock is not correspond in production location.' #TOFIX: consume stock is not updated in default production location of product.
assert production_location.stock_real == 1*4, 'consume stock does not correspond in production location.' #TOFIX: consume stock is not updated in default production location of product.
-
I check availabile stock after consumed and scraped.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_icecream'), context=context)
assert product.qty_available == 131.96, "Stock is not correspond."
assert round(product.virtual_available, 2) == 1.96, "Vitual stock is not correspond."
assert product.qty_available == 131.96, "Stock does not correspond."
assert round(product.virtual_available, 2) == 1.96, "Vitual stock does not correspond."
-
I trace all incoming lots.
-
@ -259,5 +276,5 @@
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_icecream'), context=context)
assert round(product.qty_available, 2) == 1.96, "Stock is not correspond."
assert round(product.virtual_available, 2) == 1.96, "Vitual stock is not correspond."
assert round(product.qty_available, 2) == 1.96, "Stock does not correspond."
assert round(product.virtual_available, 2) == 1.96, "Vitual stock does not correspond."