[IMP] price_unit_out on stock move matchings as related field, adding test for cost method changing and returns, adding related field to stock move report
bzr revid: jco@openerp.com-20130525131639-z3ekab24sp1jw91y
This commit is contained in:
@@ -67,6 +67,8 @@ Dashboard / Reports for Purchase Management will include:
|
||||
'res_config_view.xml',
|
||||
],
|
||||
'test': [
|
||||
'test/fifo_returns.yml',
|
||||
'test/costmethodchange.yml',
|
||||
'test/process/cancel_order.yml',
|
||||
'test/process/rfq2order2done.yml',
|
||||
'test/process/generate_invoice_from_reception.yml',
|
||||
|
||||
@@ -75,10 +75,10 @@
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Check the standard price TODO the error message if the assert fails is wrong
|
||||
Check the standard price
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_average_icecream")).standard_price == 75.0, 'After second reception, the standard price should be the weighted average of the 2 receptions'
|
||||
assert self.browse(cr, uid, ref("product_average_icecream")).standard_price == 75.0, 'After second reception, we should have an average price of 75.0 on the product'
|
||||
-
|
||||
Create picking to send some goods
|
||||
-
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
-
|
||||
Set a product as using average price.
|
||||
-
|
||||
!record {model: product.product, id: product_variable_icecream}:
|
||||
default_code: AVG
|
||||
name: Variable Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
procure_method: make_to_stock
|
||||
valuation: real_time
|
||||
cost_method: average
|
||||
property_stock_account_input: account.o_expense
|
||||
property_stock_account_output: account.o_income
|
||||
description: Average 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.
|
||||
-
|
||||
I create a draft Purchase Order for first incoming shipment for 10 pieces at 60€
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_variable1}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_variable_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 60.0
|
||||
name: 'Average Ice Cream'
|
||||
-
|
||||
I create a draft Purchase Order for second incoming shipment for 30 pieces at 80€
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_variable2}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_variable_icecream
|
||||
product_qty: 30.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 80.0
|
||||
name: 'Average Ice Cream'
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_variable1}
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_variable2}
|
||||
-
|
||||
I check the "Approved" status of purchase order 1
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_variable1}:
|
||||
- state == 'approved'
|
||||
-
|
||||
Process the reception of purchase order 1
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_variable1")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Check the standard price of the product (variable icecream).
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
product = self.browse(cr, uid, ref("product_variable_icecream"))
|
||||
assert product.qty_available == 10.0, 'Wrong quantity in stock after first reception'
|
||||
assert product.standard_price == 60.0, 'Standard price should be the price of the first reception!'
|
||||
-
|
||||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_variable2")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Check the standard price and change the product's cost method towards fifo
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_variable_icecream")).standard_price == 75.0, 'After second reception, we should have an average price of 75.0 on the product'
|
||||
self.write(cr, uid, [ref("product_variable_icecream")], {'cost_method': 'fifo'}, context=context)
|
||||
-
|
||||
Create picking to send some goods
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_variable_shipment}:
|
||||
type: out
|
||||
-
|
||||
Create move for picking
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_variable_icecream}:
|
||||
picking_id: outgoing_variable_shipment
|
||||
product_id: product_variable_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_qty: 20.0
|
||||
type: out
|
||||
-
|
||||
I confirm outgoing shipment of 20 kg of Variable Ice Cream.
|
||||
-
|
||||
!workflow {model: stock.picking, action: button_confirm, ref: outgoing_variable_shipment}
|
||||
-
|
||||
Process the delivery of the outgoing shipment
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [ref("outgoing_variable_shipment")], 'default_type':'out'})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Check the standard price (60 * 10 + 30 * 80) / 40 = 75.0 did not change
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_variable_icecream")).standard_price == 75.0, 'Standard price should not have changed with outgoing picking!'
|
||||
assert self.browse(cr, uid, ref("product_variable_icecream")).qty_available == 20.0, 'Pieces were not picked correctly as the quantity on hand is wrong'
|
||||
-
|
||||
I create a draft Purchase Order for third incoming shipment for 20 pieces at 60 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_variable3}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_variable_icecream
|
||||
product_qty: 20.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 60.0
|
||||
name: 'Variable Ice Cream'
|
||||
-
|
||||
I confirm this third purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_variable3}
|
||||
-
|
||||
Process the reception of purchase order 3
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_variable3")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Change the cost method back to average and check price on product changed back to 70.0 = (20*60 + 10*60 + 30*80) / 60
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
self.write(cr, uid, [ref("product_variable_icecream")], {'cost_method': 'average'}, context=context)
|
||||
assert self.browse(cr, uid, ref("product_variable_icecream")).standard_price == 70.0, 'After third reception and changing to average again, we should have an average price of 70.0 on the product'
|
||||
@@ -17,7 +17,7 @@
|
||||
property_stock_account_output: account.o_income
|
||||
description: FIFO 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.
|
||||
-
|
||||
I create a draft Purchase Order for first in move for 10 kg at 60 euro
|
||||
I create a draft Purchase Order for first in move for 10 kg at 50 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo1}:
|
||||
partner_id: base.res_partner_3
|
||||
@@ -52,12 +52,15 @@
|
||||
!assert {model: purchase.order, id: purchase_order_fifo1}:
|
||||
- state == 'approved'
|
||||
-
|
||||
Process the reception of purchase order 1
|
||||
Process the reception of purchase order 1 and set date
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_fifo1")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
from datetime import *
|
||||
#for move in pick_ids[0].move_lines:
|
||||
# self.pool.get("stock.move").write(cr, uid, [move.id], {'date': (datetime.now()+timedelta(1)).strftime('%Y-%m-%d %H:%M:%S')})
|
||||
-
|
||||
Check the standard price of the product (fifo icecream)
|
||||
-
|
||||
@@ -181,7 +184,7 @@
|
||||
-
|
||||
Purchase order in usd
|
||||
-
|
||||
I create a draft Purchase Order for 30000 g at 0.150 USD/g and 10 kg at 150 USD/kg
|
||||
I create a draft Purchase Order for 30 kg at 0.150 USD/g and 10 kg at 150 USD/kg
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo_usd}:
|
||||
partner_id: base.res_partner_3
|
||||
@@ -213,7 +216,7 @@
|
||||
print partial_id
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
We create delivery order of 49500 gram
|
||||
We create delivery order of 49.500 kg
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_cur}:
|
||||
type: out
|
||||
@@ -223,8 +226,8 @@
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_cur}:
|
||||
picking_id: outgoing_fifo_shipment_cur
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_gram
|
||||
product_qty: 49500
|
||||
product_uom: product.product_uom_kgm
|
||||
product_qty: 49.500
|
||||
type: out
|
||||
-
|
||||
Process the delivery of the outgoing shipment
|
||||
@@ -248,7 +251,7 @@
|
||||
for move in self.pool.get("stock.move").browse(cr, uid, list):
|
||||
print move.price_unit, move.product_qty, move.qty_remaining, move.type, move.date
|
||||
-
|
||||
Do a delivery of an extra 10 kg INSTEAD OF RETURN
|
||||
Do a delivery of an extra 10 kg
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_ret}:
|
||||
type: out
|
||||
@@ -275,7 +278,6 @@
|
||||
Check rounded price is 150.0 / 1.2834
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
print round(self.browse(cr, uid, ref("product_fifo_icecream")).standard_price, 2)
|
||||
assert round(self.browse(cr, uid, ref("product_fifo_icecream")).standard_price) == round(150.0 / 1.2834)
|
||||
-
|
||||
Print price
|
||||
@@ -288,7 +290,7 @@
|
||||
for move in self.pool.get("stock.move").browse(cr, uid, list):
|
||||
print move.price_unit, move.product_qty, move.qty_remaining, move.type, move.date
|
||||
-
|
||||
Let us create some outs to get negative stock. Create outpicking. We create delivery order of 100 kg
|
||||
Let us create some outs to get negative stock. Create outpicking. We create delivery order of 200 kg, but will pick only 100 kg
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_neg}:
|
||||
type: out
|
||||
@@ -299,10 +301,10 @@
|
||||
picking_id: outgoing_fifo_shipment_neg
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_qty: 100.0
|
||||
product_qty: 200.0
|
||||
type: out
|
||||
-
|
||||
Let us create another out of 50 kg
|
||||
Let us create another out of 400 kg, but will pick only 50 kg
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_neg2}:
|
||||
type: out
|
||||
@@ -313,15 +315,19 @@
|
||||
picking_id: outgoing_fifo_shipment_neg2
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_qty: 50.0
|
||||
product_qty: 400.0
|
||||
type: out
|
||||
-
|
||||
Process the delivery of the outgoing shipments
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [ref("outgoing_fifo_shipment_neg")], 'default_type':'out'})
|
||||
line = self.browse(cr, uid, partial_id, context=context).move_ids[0]
|
||||
self.pool.get("stock.partial.picking.line").write(cr, uid, [line.id], {'quantity':100})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [ref("outgoing_fifo_shipment_neg2")], 'default_type':'out'})
|
||||
line = self.browse(cr, uid, partial_id, context=context).move_ids[0]
|
||||
self.pool.get("stock.partial.picking.line").write(cr, uid, [line.id], {'quantity':50})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Print price
|
||||
@@ -334,7 +340,7 @@
|
||||
for move in self.pool.get("stock.move").browse(cr, uid, list):
|
||||
print move.price_unit, move.product_qty, move.qty_remaining, move.type, move.date
|
||||
-
|
||||
Receive purchase order with 100 kgm FIFO Ice Cream at 50 euro/kg
|
||||
Receive purchase order with 50 kg FIFO Ice Cream at 50 euro/kg
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo_neg}:
|
||||
partner_id: base.res_partner_3
|
||||
@@ -342,7 +348,7 @@
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_fifo_icecream
|
||||
product_qty: 100.0
|
||||
product_qty: 50.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 50.0
|
||||
name: 'FIFO Ice Cream'
|
||||
@@ -358,13 +364,12 @@
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Assert price on product is 50
|
||||
Assert price on product is still the old price as the out move has not been received fully yet
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_fifo_icecream")).standard_price == 50.0
|
||||
|
||||
assert round(self.browse(cr, uid, ref("product_fifo_icecream")).standard_price) == round(150.0 / 1.2834)
|
||||
-
|
||||
Receive purchase order with 100 kgm FIFO Ice Cream at 80 euro/kg
|
||||
Receive purchase order with 60 kg FIFO Ice Cream at 80 euro/kg
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo_neg2}:
|
||||
partner_id: base.res_partner_3
|
||||
@@ -372,7 +377,7 @@
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_fifo_icecream
|
||||
product_qty: 100.0
|
||||
product_qty: 60.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 80
|
||||
name: 'FIFO Ice Cream'
|
||||
@@ -388,12 +393,12 @@
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Print price
|
||||
The price of the product should have changed back to 65.0
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
print self.browse(cr, uid, ref("product_fifo_icecream")).standard_price
|
||||
print self.browse(cr, uid, ref("product_fifo_icecream")).qty_available
|
||||
print self.browse(cr, uid, ref("product_fifo_icecream"), context={'force_company': 1}).qty_available
|
||||
list = self.pool.get("stock.move").search(cr, uid, [('product_id','=', ref("product_fifo_icecream"))])
|
||||
list = self.pool.get("stock.move").search(cr, uid, [('product_id','=', ref("product_fifo_icecream"))], order='date')
|
||||
for move in self.pool.get("stock.move").browse(cr, uid, list):
|
||||
print move.price_unit, move.product_qty, move.qty_remaining, move.type, move.date
|
||||
@@ -0,0 +1,97 @@
|
||||
-
|
||||
Set a product as using fifo price
|
||||
-
|
||||
!record {model: product.product, id: product_fiforet_icecream}:
|
||||
default_code: FIFO
|
||||
name: FIFO Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
procure_method: make_to_stock
|
||||
cost_method: fifo
|
||||
valuation: real_time
|
||||
property_stock_account_input: account.o_expense
|
||||
property_stock_account_output: account.o_income
|
||||
description: FIFO 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.
|
||||
-
|
||||
I create a draft Purchase Order for first in move for 10 kg at 50 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fiforet1}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_fiforet_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 50.0
|
||||
name: 'FIFO Ice Cream'
|
||||
-
|
||||
I create a draft Purchase Order for second shipment for 30 kg at 80 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fiforet2}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_fiforet_icecream
|
||||
product_qty: 30.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 80.0
|
||||
name: 'FIFO Ice Cream'
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fiforet1}
|
||||
-
|
||||
Process the reception of purchase order 1
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_fiforet1")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Check the standard price of the product (fifo icecream)
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_fiforet_icecream")).standard_price == 0.0, 'Standard price should not have changed!'
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fiforet2}
|
||||
-
|
||||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_fiforet2")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Return the goods of purchase order 2
|
||||
-
|
||||
!python {model: stock.return.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_fiforet2")).picking_ids
|
||||
return_id = self.create(cr, uid, {}, context={'active_model':'stock.picking', 'active_id': pick_ids[0].id})
|
||||
res = self.create_returns(cr, uid, [return_id], context={'active_id': pick_ids[0].id})
|
||||
return_move_id = self.pool.get("stock.move").search(cr, uid, [('move_returned_from','=', pick_ids[0].move_lines[0].id)])
|
||||
|
||||
movepick_obj = self.pool.get("stock.partial.move")
|
||||
movepick_id = movepick_obj.create(cr, uid, {}, context={'active_model': 'stock.move', 'active_ids': return_move_id})
|
||||
movepick_obj.do_partial(cr, uid, [movepick_id])
|
||||
-
|
||||
Check the standard price of the product (fifo return icecream) changed to 70.0
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
print self.browse(cr, uid, ref("product_fiforet_icecream")).standard_price
|
||||
assert self.browse(cr, uid, ref("product_fiforet_icecream")).standard_price == 70.0, 'Standard price should have changed to 70.0!'
|
||||
-
|
||||
Print price
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
print self.browse(cr, uid, ref("product_fiforet_icecream")).standard_price
|
||||
print self.browse(cr, uid, ref("product_fiforet_icecream")).qty_available
|
||||
print self.browse(cr, uid, ref("product_fiforet_icecream"), context={'force_company': 1}).qty_available
|
||||
list = self.pool.get("stock.move").search(cr, uid, [('product_id','=', ref("product_fiforet_icecream"))])
|
||||
for move in self.pool.get("stock.move").browse(cr, uid, list):
|
||||
print move.price_unit, move.product_qty, move.qty_remaining, move.type, move.date
|
||||
@@ -261,8 +261,7 @@ class report_stock_valuation(osv.osv):
|
||||
'location_dest_type': fields.selection([('supplier', 'Supplier Location'), ('view', 'View'), ('internal', 'Internal Location'), ('customer', 'Customer Location'), ('inventory', 'Inventory'), ('procurement', 'Procurement'), ('production', 'Production'), ('transit', 'Transit Location for Inter-Companies Transfers')], 'Location Destination Type', required=True),
|
||||
'location_src_type': fields.selection([('supplier', 'Supplier Location'), ('view', 'View'), ('internal', 'Internal Location'), ('customer', 'Customer Location'), ('inventory', 'Inventory'), ('procurement', 'Procurement'), ('production', 'Production'), ('transit', 'Transit Location for Inter-Companies Transfers')], 'Location Source Type', required=True),
|
||||
'match': fields.many2one('stock.move', 'Match', readonly=True),
|
||||
'related': fields.related('match', 'picking_id', 'name', type='text', readonly=True),
|
||||
#'funct': fields.function(_get_properties, type='float')
|
||||
'related_move_in': fields.related('match', 'picking_id', 'name', type='text', readonly=True),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
<field name="prodlot_id" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="related_move_in"/>
|
||||
<field name="date"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="product_qty" sum="Total quantity"/>
|
||||
@@ -247,6 +248,7 @@
|
||||
<field name="location_type"/>
|
||||
<field name="date"/>
|
||||
<filter icon="terp-go-home" name="location_type_scrap" string="Scrap" domain="[('scrap_location','=','True')]"/>
|
||||
<filter name="inmovesremaining" string="In moves remaining" domain="[('location_src_type','!=','internal'), ('location_dest_type','=', 'internal'), ()]"/>
|
||||
</group>
|
||||
<group expand="1" string="Group By..." >
|
||||
<filter string="Partner" name="group_partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
@@ -280,9 +282,28 @@
|
||||
the stock is calculated based on the prices of the stock moves depending on the costing method of the product (FIFO/LIFO/Average/standard) and not just the standard price of the product.
|
||||
Sort and group by selection criteria in order to better analyse and manage your company activities. </field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="action_stock_inventory_valuation_report" model="ir.actions.act_window">
|
||||
<field name="name">Stock Inventory Valuation Analysis</field>
|
||||
<field name="res_model">report.stock.valuation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="search_view_id" eval="False"/>
|
||||
<field name="context">{'contact_display': 'partner', 'search_default_real':1,
|
||||
'search_default_year':1,'search_default_month':1, 'search_default_location_type_internal':1,'search_default_group_product':1,'group_by':[], 'group_by_no_leaf':0}</field>
|
||||
<field name="help">Stock Valuation Analysis allows you to easily check and analyse your company stock levels. The difference with the Inventory Analysis, is that
|
||||
the stock is calculated based on the prices of the stock moves depending on the costing method of the product (FIFO/LIFO/Average/standard) and not just the standard price of the product.
|
||||
Sort and group by selection criteria in order to better analyse and manage your company activities. </field>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem action="action_stock_inventory_valuation_report"
|
||||
id="menu_action_stock_inventory_valuation_report"
|
||||
parent="next_id_61" sequence="4"/>
|
||||
<menuitem action="action_stock_valuation_report"
|
||||
id="menu_action_stock_valuation_report"
|
||||
parent="next_id_61" sequence="4"/>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
+25
-20
@@ -2676,7 +2676,7 @@ class stock_move(osv.osv):
|
||||
and creates the necessary stock move matchings
|
||||
|
||||
It returns a list of tuples with (move_id, match_id)
|
||||
|
||||
which can be used for generating the accounting entries
|
||||
'''
|
||||
product_obj = self.pool.get('product.product')
|
||||
currency_obj = self.pool.get('res.currency')
|
||||
@@ -2698,6 +2698,9 @@ class stock_move(osv.osv):
|
||||
product = product_obj.browse(cr, uid, move.product_id.id, context=ctx)
|
||||
cost_method = product.cost_method
|
||||
product_uom_qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
|
||||
if not product.id in product_avail:
|
||||
product_avail[product.id] = product.qty_available
|
||||
|
||||
|
||||
# Check if out -> do stock move matchings and if fifo/lifo -> update price
|
||||
if move.location_id.usage == 'internal' and move.location_dest_id.usage != 'internal':
|
||||
@@ -2705,13 +2708,12 @@ class stock_move(osv.osv):
|
||||
fifo = (cost_method != 'lifo')
|
||||
tuples = product_obj.get_stock_matchings_fifolifo(cr, uid, [product.id], product_qty, fifo,
|
||||
product_uom, move.company_id.currency_id.id, context=ctx) #TODO Would be better to use price_currency_id for migration?
|
||||
print tuples
|
||||
price_amount = 0.0
|
||||
amount = 0.0
|
||||
#Write stock matchings
|
||||
for match in tuples:
|
||||
matchvals = {'move_in_id': match[0], 'qty': match[1],
|
||||
'move_out_id': move.id, 'price_unit_out': match[2]}
|
||||
'move_out_id': move.id}
|
||||
match_id = matching_obj.create(cr, uid, matchvals, context=context)
|
||||
res[move.id].append(match_id)
|
||||
move_in = self.browse(cr, uid, match[0], context=context)
|
||||
@@ -2725,10 +2727,10 @@ class stock_move(osv.osv):
|
||||
new_price = uom_obj._compute_price(cr, uid, product.uom_id.id, product.standard_price,
|
||||
product_uom)
|
||||
self.write(cr, uid, move.id, {'price_unit': new_price}, context=ctx)
|
||||
|
||||
|
||||
if not product.id in product_avail:
|
||||
product_avail[product.id] = product.qty_available
|
||||
#Adjust product_avail when not average and move returned from
|
||||
if (not move.move_returned_from or product.cost_method != 'average'):
|
||||
product_avail[product.id] -= product_uom_qty
|
||||
|
||||
#Check if in => if price 0.0, take standard price / Update price when average price and price on move != standard price
|
||||
if move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal':
|
||||
if move.price_unit == 0.0:
|
||||
@@ -2743,25 +2745,26 @@ class stock_move(osv.osv):
|
||||
else:
|
||||
new_std_price = move.price_unit
|
||||
product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price}, context=ctx)
|
||||
# Should create the stock move matchings for outs for the negative stock
|
||||
# Should create the stock move matchings for previous outs for the negative stock that can be matched with is in
|
||||
if product_avail[product.id] < 0.0:
|
||||
#Search for most recent out moves until at least one matching has been found => order date desc
|
||||
moves = self.search(cr, uid, [('company_id', '=', move.company_id.id), ('state','=', 'done'), ('location_id.usage','=','internal'), ('location_dest_id.usage', '!=', 'internal'),
|
||||
('product_id', '=', move.product_id.id), ('qty_remaining', '>', 0.0)], order='date', context=ctx)
|
||||
qty_to_go = move.product_qty
|
||||
for out_mov in self.browse(cr, uid, moves, context=ctx):
|
||||
if qty_to_go <= 0.0:
|
||||
break
|
||||
out_qty_converted = uom_obj._compute_qty(cr, uid, out_mov.product_uom.id, out_mov.qty_remaining, move.product_uom.id)
|
||||
qty = 0.0
|
||||
if out_qty_converted <= qty_to_go:
|
||||
qty = out_qty_converted
|
||||
elif qty_to_go > 0.0:
|
||||
qty = qty_to_go
|
||||
new_price = uom_obj._compute_price(cr, uid, move.product_uom.id, move.price_unit,
|
||||
out_mov.product_uom.id)
|
||||
matchvals = {'move_in_id': move.id, 'qty': qty,
|
||||
'move_out_id': out_mov.id, 'price_unit_out': new_price}
|
||||
#revert_qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, qty, out_mov.product_uom.id)
|
||||
revert_qty = (qty / out_qty_converted) * out_mov.qty_remaining
|
||||
matchvals = {'move_in_id': move.id, 'qty': revert_qty,
|
||||
'move_out_id': out_mov.id}
|
||||
match_id = matching_obj.create(cr, uid, matchvals, context=context)
|
||||
|
||||
res[move.id].append(match_id)
|
||||
qty_to_go -= qty
|
||||
#Need to re-calculate total price of every out_move if FIFO/LIFO
|
||||
@@ -2770,15 +2773,17 @@ class stock_move(osv.osv):
|
||||
amount = 0.0
|
||||
total_price = 0.0
|
||||
for match in matching_obj.browse(cr, uid, matches, context=context):
|
||||
in_mov = self.browse(cr, uid, match.move_in_id.id, context=context)
|
||||
amount += uom_obj._compute_qty(cr, uid, in_mov.product_uom.id, in_mov.product_qty, out_mov.product_uom.id)
|
||||
total_price += amount * uom_obj._compute_price(cr, uid, in_mov.product_uom.id, in_mov.price_unit, out_mov.product_uom.id)
|
||||
amount += match.qty
|
||||
total_price += match.qty * match.price_unit_out
|
||||
if amount > 0.0:
|
||||
self.write(cr, uid, [out_mov.id], {'price_unit': total_price / amount}, context=context)
|
||||
product_obj.write(cr, uid, [product.id], {'standard_price': total_price / amount}, context=ctx)
|
||||
|
||||
product_avail[product.id] += product.qty_available
|
||||
|
||||
if amount >= out_mov.product_qty:
|
||||
print "Update product when negative"
|
||||
print total_price
|
||||
print amount
|
||||
print out_mov.product_qty
|
||||
product_obj.write(cr, uid, [product.id], {'standard_price': total_price / amount}, context=ctx)
|
||||
product_avail[product.id] += product_uom_qty
|
||||
#The return of average products at average price could be made optional
|
||||
if move.location_id.usage == 'internal' and move.location_dest_id.usage != 'internal' and cost_method == 'average' and move.move_returned_from:
|
||||
move_orig = move.move_returned_from
|
||||
|
||||
@@ -38,27 +38,25 @@ class product_product (osv.osv):
|
||||
#TODO Need force_company in context?
|
||||
if 'force_company' not in context:
|
||||
company_id = self.pool.get("res.users").browse(cr, uid, uid, context=context).company_id.id
|
||||
|
||||
else:
|
||||
company_id = context['force_company']
|
||||
for prod in self.browse(cr, uid, ids, context=context):
|
||||
if prod.cost_method == 'average':
|
||||
mov_ids = move_obj.search(cr, uid, [('company_id', '=', company_id), ('state', '=', 'done'), ('qty_remaining', '>', 0.0)], context=context)
|
||||
mov_ids = move_obj.search(cr, uid, [('product_id', '=', prod.id), ('company_id', '=', company_id), ('state', '=', 'done'), ('qty_remaining', '>', 0.0)], context=context)
|
||||
#TODO Need to convert to all prices to UoM of stock move
|
||||
for move in move_obj.browse(cr, uid, mov_ids, context=context):
|
||||
new_price = uom_obj._compute_price(cr, uid, move.product_uom.id, move.price_unit, prod.uom_id.id)
|
||||
move.write({'price_unit': new_price})
|
||||
new_price = uom_obj._compute_price(cr, uid, move.product_uom.id, prod.standard_price, prod.uom_id.id)
|
||||
move_obj.write(cr, uid, [move.id], {'price_unit': new_price}, context=context)
|
||||
#We need to recalculate the average on all products from all stock moves in
|
||||
if vals["cost_method"] == 'average':
|
||||
#Search all done in moves
|
||||
mov_ids = move_obj.search(cr, uid, [('company_id', '=', company_id), ('state', '=', 'done'), ('location_dest_id.usage', '=', 'internal'),
|
||||
mov_ids = move_obj.search(cr, uid, [('product_id', '=', prod.id), ('company_id', '=', company_id), ('state', '=', 'done'), ('location_dest_id.usage', '=', 'internal'),
|
||||
('location_id.usage', '!=', 'internal')], context=context)
|
||||
qty = 0.0
|
||||
total_price = 0.0
|
||||
for move in move_obj.browse(cr, uid, mov_ids, context=context):
|
||||
total_price += move.product_qty * move.price_unit
|
||||
qty += uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, prod.uom_id.id)
|
||||
print "Qty and Total Price:", qty, total_price
|
||||
if qty > 0.0:
|
||||
prod_obj.write(cr, uid, [prod.id], {'standard_price': total_price / qty}, context=context)
|
||||
res = super(product_product, self).write(cr, uid, ids, vals, context=context)
|
||||
@@ -158,16 +156,13 @@ class stock_move(osv.osv):
|
||||
if move_in:
|
||||
#Search all matchings
|
||||
matches = match_obj.search(cr, uid, [('move_in_id', '=', move.id)], context=context)
|
||||
print [(x.move_in_id.product_qty, x.qty, x.move_in_id.price_unit, x.move_in_id.date) for x in match_obj.browse(cr, uid, matches, context=context)]
|
||||
|
||||
qty = move.product_qty
|
||||
for match in match_obj.browse(cr, uid, matches, context=context):
|
||||
qty -= uom_obj._compute_qty(cr, uid, match.move_out_id.product_uom.id, match.qty, move.product_uom.id)
|
||||
res[move.id] = qty
|
||||
elif move_out:
|
||||
#Search all matchings, but from the other side
|
||||
#Search all matchings, but from the out side
|
||||
matches = match_obj.search(cr, uid, [('move_out_id', '=', move.id)], context=context)
|
||||
print [(x.move_in_id.product_qty, x.qty, x.move_in_id.price_unit, x.move_in_id.date) for x in match_obj.browse(cr, uid, matches, context=context)]
|
||||
qty = move.product_qty
|
||||
for match in match_obj.browse(cr, uid, matches, context=context):
|
||||
qty -= match.qty
|
||||
@@ -182,15 +177,40 @@ class stock_move(osv.osv):
|
||||
}
|
||||
|
||||
|
||||
|
||||
class stock_move_matching(osv.osv):
|
||||
_name = "stock.move.matching"
|
||||
_description = "Stock move matchings"
|
||||
|
||||
|
||||
def _get_unit_price_out (self, cr, uid, ids, field_names, arg, context=None):
|
||||
res = {}
|
||||
uom_obj = self.pool.get("product.uom")
|
||||
for match in self.browse(cr, uid, ids, context=context):
|
||||
res[match.id] = uom_obj._compute_price(cr, uid, match.move_in_id.product_uom.id, match.price_unit, match.move_out_id.product_uom.id)
|
||||
return res
|
||||
|
||||
def _get_matches(self, cr, uid, ids, context=None):
|
||||
res = []
|
||||
move_obj = self.pool.get("stock.move")
|
||||
for move in move_obj.browse(cr, uid, ids, context=context):
|
||||
move_in = move.location_id and move.location_dest_id and move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal'
|
||||
if move_in:
|
||||
matches = self.pool.get("stock.move.matching").search(cr, uid, [('move_in_id', '=', move.id)], context=context)
|
||||
res += matches
|
||||
return res
|
||||
|
||||
def _get_selfmatches(self, cr, uid, ids, context=None):
|
||||
return ids
|
||||
|
||||
|
||||
_columns = {
|
||||
'move_in_id': fields.many2one('stock.move', 'Stock move in', required=True),
|
||||
'move_out_id': fields.many2one('stock.move', 'Stock move out', required=True),
|
||||
'qty': fields.float('Quantity in UoM of out', required=True),
|
||||
'price_unit':fields.related('move_in_id', 'price_unit', string="Unit price", type="float"),
|
||||
'price_unit_out': fields.float('Unit price out'),
|
||||
'price_unit_out': fields.function(_get_unit_price_out, type="float", string="Price in UoM of out move",
|
||||
store = {'stock.move': (_get_matches, ['price_unit', 'product_uom'], 10),
|
||||
'stock.move.matching': (_get_selfmatches, ['move_in_id', 'qty', 'move_out_id'], 10)},),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user