[ADD] Add stock fifo lifo module, add average tests and improve average calc
bzr revid: jco@openerp.com-20130426111730-pf96a6nh2wv42kwl
This commit is contained in:
parent
042e47c5d4
commit
c2c8c476e5
|
@ -77,6 +77,7 @@ Dashboard / Reports for Purchase Management will include:
|
|||
'test/ui/print_report.yml',
|
||||
'test/ui/duplicate_order.yml',
|
||||
'test/ui/delete_order.yml',
|
||||
'test/average_price.yml',
|
||||
],
|
||||
'demo': [
|
||||
'purchase_order_demo.yml',
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
-
|
||||
Set a product as using average price
|
||||
-
|
||||
!record {model: product.product, id: product_average_icecream}:
|
||||
default_code: AVG
|
||||
name: Average Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
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 in move
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_average1}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_average_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 shipment for 30 pieces at 80 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_average2}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_average_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_average1}
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_average2}
|
||||
-
|
||||
I check the "Approved" status of purchase order 1
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_average1}:
|
||||
- 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_average1")).picking_ids
|
||||
print pick_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 (average icecream)
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
print self.browse(cr, uid, ref("product_average_icecream")).qty_available
|
||||
assert self.browse(cr, uid, ref("product_average_icecream")).standard_price == 60.0, 'Standard price should not change while receiving products!'
|
||||
-
|
||||
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_average2")).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
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_average_icecream")).standard_price == 75.0, 'Standard price should not change while receiving products!'
|
||||
-
|
||||
Let us send some goods
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_average_shipment}:
|
||||
type: out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_average_icecream}:
|
||||
picking_id: outgoing_average_shipment
|
||||
product_id: product_average_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_qty: 20.0
|
||||
-
|
||||
I confirm outgoing shipment of 20 kg of Average Ice Cream. @TODO need to send the pieces still!
|
||||
-
|
||||
!workflow {model: stock.picking, action: button_confirm, ref: outgoing_average_shipment}
|
||||
-
|
||||
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_average_icecream")).standard_price == 75.0, 'Standard price as average price of second reception incorrect!'
|
|
@ -2610,11 +2610,12 @@ class stock_move(osv.osv):
|
|||
product_price = partial_data.get('product_price',0.0)
|
||||
product_currency = partial_data.get('product_currency',False)
|
||||
product = product_obj.browse(cr, uid, move.product_id.id, context=context)
|
||||
|
||||
#Check we are using the right company
|
||||
company_id = move.company_id.id
|
||||
ctx = context.copy()
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
company_id = move.company_id.id
|
||||
if company_id != user.company_id.id:
|
||||
#test if I could do this with a read instead
|
||||
ctx['force_company'] = move.company_id.id
|
||||
product = product_obj.browse(cr, uid, move.product_id.id, context=ctx)
|
||||
cost_method = product.cost_method
|
||||
|
@ -2622,14 +2623,19 @@ class stock_move(osv.osv):
|
|||
#Check if cost_method used needs update
|
||||
avg_in_update = (move.picking_id.type == 'in') and (cost_method == 'average')
|
||||
avg_out_update = (move.picking_id.type == 'out') and (cost_method == 'average') and (move.move_returned_from)
|
||||
if avg_in_update or avg_out_update:
|
||||
if avg_in_update or avg_out_update:
|
||||
|
||||
# If no price from picking, use cost price from product
|
||||
if product_price == 0:
|
||||
product_price = product.price_get('standard_price', context=ctx)[product.id]
|
||||
|
||||
move_currency_id = move.company_id.currency_id.id
|
||||
ctx['currency_id'] = move_currency_id
|
||||
qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
|
||||
first_avail = False
|
||||
|
||||
if not product.id in product_avail:
|
||||
product_avail[product.id] = product.qty_available
|
||||
first_avail = True
|
||||
|
||||
if qty > 0:
|
||||
if avg_in_update:
|
||||
new_price = currency_obj.compute(cr, uid, product_currency,
|
||||
|
@ -2652,15 +2658,13 @@ class stock_move(osv.osv):
|
|||
amount_unit = product.price_get('standard_price', context=ctx)[product.id]
|
||||
new_std_price = ((amount_unit * product_avail[product.id])\
|
||||
- (new_price * qty))/(product_avail[product.id] - qty)
|
||||
# Write the field according to price type field
|
||||
product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price})
|
||||
|
||||
#Adjust product available with the right amount
|
||||
if not first_avail:
|
||||
if avg_out_update:
|
||||
product_avail[product.id] -= qty
|
||||
else:
|
||||
product_avail[product.id] += qty
|
||||
|
||||
if avg_out_update:
|
||||
product_avail[product.id] -= qty
|
||||
else:
|
||||
product_avail[product.id] += qty
|
||||
|
||||
# Write the field according to price type field, company dependence in ctx
|
||||
product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price}, context=ctx)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2013 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from stock_fifo_lifo import *
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{
|
||||
'name': 'FIFO/LIFO stock valuation',
|
||||
'version': '0.1',
|
||||
'author': 'OpenERP SA',
|
||||
'summary': 'Valorize your stock FIFO/LIFO',
|
||||
'description' : """
|
||||
Manage FIFO/LIFO stock valuation
|
||||
================================
|
||||
|
||||
This gives reports which value the stock in a FIFO/LIFO way. It adds a table to match the outs with the ins.
|
||||
""",
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': [],
|
||||
'depends': ['purchase'],
|
||||
'category': 'Warehouse Management',
|
||||
'sequence': 16,
|
||||
'demo': [
|
||||
],
|
||||
'data': ['security/ir.model.access.csv'
|
||||
],
|
||||
'test': ['test/fifolifo_price.yml',
|
||||
'test/lifo_price.yml'
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
'css': [],
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_stock_move_matching_manager,stock.move.matching manager,model_stock_move_matching,stock.group_stock_manager,1,1,1,1
|
||||
access_stock_move_matching_user,stock.move.matching user,model_stock_move_matching,stock.group_stock_user,1,1,1,0
|
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
<!-- multi -->
|
||||
|
||||
<record model="ir.rule" id="stock_matching_rule">
|
||||
<field name="name">stock_move matching</field>
|
||||
<field name="model_id" search="[('model','=','stock.move.matching')]" model="ir.model"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">[('1','=','1')]</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,139 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
|
||||
#@TODO Should not this be product template?
|
||||
class product_product (osv.osv):
|
||||
_name = "product.product"
|
||||
_inherit = "product.product"
|
||||
_columns = {
|
||||
'cost_method': fields.property('', type='selection', view_load=True, selection = [('standard','Standard Price'), ('average','Average Price'),
|
||||
('fifo', 'FIFO price'), ('lifo', 'LIFO price')],
|
||||
help="""Standard Price: The cost price is manually updated at the end of a specific period (usually every year).
|
||||
Average Price: The cost price is recomputed at each incoming shipment.
|
||||
FIFO: When cost is calculated the FIFO way.
|
||||
LIFO: When cost is calculated the LIFO way. """,
|
||||
string="Costing Method"),
|
||||
}
|
||||
|
||||
|
||||
def get_stock_matchings_fifolifo(self, cr, uid, ids, qty, fifo, context=None):
|
||||
'''
|
||||
This method returns a list of tuples for which the stock moves are working fifo/lifo
|
||||
This should be called for only one product at a time
|
||||
-> might still need to add UoM
|
||||
'''
|
||||
assert len(ids) == 1, 'Only the fifolifo stock moves of one product can be calculated at a time.'
|
||||
product = self.browse(cr, uid, ids, context=context)[0]
|
||||
move_obj = self.pool.get('stock.move')
|
||||
|
||||
if fifo:
|
||||
move_ids = move_obj.search(cr, uid, [('qty_remaining', '>', 0), ('state', '=', 'done'),
|
||||
('type', '=', 'in'), ('product_id', '=', product.id)],
|
||||
order = 'date', context=context)
|
||||
else:
|
||||
move_ids = move_obj.search(cr, uid, [('qty_remaining', '>', 0), ('state', '=', 'done'),
|
||||
('type', '=', 'in'), ('product_id', '=', product.id)],
|
||||
order = 'date desc', context=context)
|
||||
tuples = []
|
||||
qty_to_go = qty
|
||||
for move in move_obj.browse(cr, uid, move_ids, context=context):
|
||||
# @TODO convert UoM for product quantities?
|
||||
product_qty = move.product_qty
|
||||
if qty_to_go - product_qty >= 0:
|
||||
tuples.append((move.id, product_qty, move.price_unit),)
|
||||
qty_to_go -= product_qty
|
||||
else:
|
||||
tuples.append((move.id, qty_to_go, move.price_unit),)
|
||||
qty_to_go = 0
|
||||
break
|
||||
return tuples
|
||||
|
||||
class stock_move(osv.osv):
|
||||
_inherit = 'stock.move'
|
||||
_columns = {'qty_remaining': fields.float("Remaining"),
|
||||
'matching_ids_in': fields.one2many('stock.move.matching', 'move_in_id'),
|
||||
'matching_ids_out':fields.one2many('stock.move.matching', 'move_out_id'),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if 'product_qty' in vals:
|
||||
vals['qty_remaining'] = vals['product_qty']
|
||||
res = super(stock_move, self).create(cr, uid, vals, context=context)
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if 'product_qty' in vals:
|
||||
vals['qty_remaining'] = vals['product_qty']
|
||||
res = super(stock_move, self).write(cr, uid, ids, vals, context=context)
|
||||
return res
|
||||
|
||||
|
||||
#@TODO overwrite method for price_computation
|
||||
def price_computation(self, cr, uid, ids, partial_datas, context=None):
|
||||
super(stock_move, self).price_computation(cr, uid, ids, partial_datas, context=context)
|
||||
product_obj = self.pool.get('product.product')
|
||||
matching_obj = self.pool.get('stock.move.matching')
|
||||
|
||||
#Find stock moves working in fifo/lifo price -> find stock moves out
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
product = product_obj.browse(cr, uid, move.product_id.id, context=context)
|
||||
|
||||
#Check we are using the right company
|
||||
company_id = move.company_id.id
|
||||
ctx = context.copy()
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
if company_id != user.company_id.id:
|
||||
ctx['force_company'] = move.company_id.id
|
||||
product = product_obj.browse(cr, uid, move.product_id.id, context=ctx)
|
||||
cost_method = product.cost_method
|
||||
product_price = move.price_unit
|
||||
#Still need to see how to handle UoM + check quantity from the partial datas
|
||||
product_qty = move.product_qty
|
||||
|
||||
if move.picking_id.type == 'out' and cost_method in ['fifo', 'lifo']:
|
||||
if not move.move_returned_from:
|
||||
tuples = product_obj.get_stock_matchings_fifolifo(cr, uid, [product.id], product_qty, cost_method == 'fifo', context=context)
|
||||
for match in tuples:
|
||||
matchvals = {'move_in_id': match[0], 'qty': match[1], 'price_unit': match[2],
|
||||
'move_out_id': move.id}
|
||||
match_id = matching_obj.create(cr, uid, matchvals, context=context)
|
||||
move_in = self.browse(cr, uid, match[0], context=context)
|
||||
self.write(cr, uid, match[0], { 'qty_remaining': move_in.qty_remaining - match[1]}, context=context)
|
||||
else:
|
||||
#We should find something to do when a stock matching is linked to the returned move already
|
||||
if move.move_returned_from.matching_ids_in:
|
||||
pass
|
||||
return True
|
||||
|
||||
class stock_move_matching(osv.osv):
|
||||
_name = "stock.move.matching"
|
||||
_description = "Stock move matchings"
|
||||
_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.integer('Quantity', required=True),
|
||||
'price_unit':fields.related('move_in_id', 'price_unit', string="Unit price", type="float"),
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
-
|
||||
Set a product as using fifo price
|
||||
-
|
||||
!record {model: product.product, id: product_fifo_icecream}:
|
||||
default_code: FIFO
|
||||
name: FIFO Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
procure_method: make_to_stock
|
||||
valuation: real_time
|
||||
cost_method: fifo
|
||||
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 pieces at 60 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo1}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_fifo_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 60.0
|
||||
name: 'FIFO Ice Cream'
|
||||
-
|
||||
I create a draft Purchase Order for second shipment for 30 pieces at 80 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo2}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_fifo_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_fifo1}
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fifo2}
|
||||
-
|
||||
I check the "Approved" status of purchase order 1
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_fifo1}:
|
||||
- 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_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])
|
||||
-
|
||||
Check the standard price of the product (fifo icecream)
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_fifo_icecream")).standard_price == 70.0, 'Standard price should not have changed!'
|
||||
-
|
||||
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_fifo2")).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 should not have changed
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_fifo_icecream")).standard_price == 70.0, 'Standard price as fifo price of second reception incorrect!'
|
||||
-
|
||||
Let us send some goods
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment}:
|
||||
type: out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream}:
|
||||
picking_id: outgoing_fifo_shipment
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_qty: 20.0
|
||||
type: out
|
||||
-
|
||||
I confirm outgoing shipment of 20 kg of FIFO Ice Cream.
|
||||
-
|
||||
!workflow {model: stock.picking, action: button_confirm, ref: outgoing_fifo_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_fifo_shipment")], 'default_type':'out'})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Check 2 stock move matchings were created
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
assert len(self.browse(cr, uid, ref("outgoing_fifo_shipment")).move_lines[0].matching_ids_out) == 2, 'Should have created 2 matchings'
|
|
@ -0,0 +1,110 @@
|
|||
-
|
||||
Set a product as using lifo price
|
||||
-
|
||||
!record {model: product.product, id: product_lifo_icecream}:
|
||||
default_code: LIFO
|
||||
name: LIFO Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
procure_method: make_to_stock
|
||||
valuation: real_time
|
||||
cost_method: lifo
|
||||
property_stock_account_input: account.o_expense
|
||||
property_stock_account_output: account.o_income
|
||||
description: LIFO 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 pieces at 60 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_lifo1}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_lifo_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 60.0
|
||||
name: 'LIFO Ice Cream'
|
||||
-
|
||||
I create a draft Purchase Order for second shipment for 30 pieces at 80 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_lifo2}:
|
||||
partner_id: base.res_partner_3
|
||||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product_lifo_icecream
|
||||
product_qty: 30.0
|
||||
product_uom: product.product_uom_categ_kgm
|
||||
price_unit: 80.0
|
||||
name: 'LIFO Ice Cream'
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lifo1}
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lifo2}
|
||||
-
|
||||
I check the "Approved" status of purchase order 1
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_lifo1}:
|
||||
- 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_lifo1")).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 (lifo icecream)
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_lifo_icecream")).standard_price == 70.0, 'Standard price should not have changed!'
|
||||
-
|
||||
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_lifo2")).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 should not have changed
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
assert self.browse(cr, uid, ref("product_lifo_icecream")).standard_price == 70.0, 'Standard price as lifo price of second reception incorrect!'
|
||||
-
|
||||
Let us send some goods
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_lifo_shipment}:
|
||||
type: out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_lifo_icecream}:
|
||||
picking_id: outgoing_lifo_shipment
|
||||
product_id: product_lifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_qty: 20.0
|
||||
type: out
|
||||
-
|
||||
I confirm outgoing shipment of 20 kg of LIFO Ice Cream.
|
||||
-
|
||||
!workflow {model: stock.picking, action: button_confirm, ref: outgoing_lifo_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_lifo_shipment")], 'default_type':'out'})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
-
|
||||
Check only 1 stock move matching was created
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
assert len(self.browse(cr, uid, ref("outgoing_lifo_shipment")).move_lines[0].matching_ids_out) == 1, 'Should have created 1 matching'
|
Loading…
Reference in New Issue