[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:
Josse Colpaert
2013-05-25 15:16:39 +02:00
parent 67ee9dfdf5
commit 7475d54cb5
9 changed files with 349 additions and 58 deletions
+2
View File
@@ -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',
+2 -2
View File
@@ -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
-
+142
View File
@@ -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'
+26 -21
View File
@@ -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
+97
View File
@@ -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
+1 -2
View File
@@ -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),
}
+22 -1
View File
@@ -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
View File
@@ -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
+32 -12
View File
@@ -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)},),
}