[IMP] Negative stock when in has to create matchings and improvements stock valuation report

bzr revid: jco@openerp.com-20130517155004-n1th4o492pa2wmee
This commit is contained in:
Josse Colpaert 2013-05-17 17:50:04 +02:00
parent c8d21a6c64
commit 7f046a7b01
4 changed files with 142 additions and 20 deletions

View File

@ -267,6 +267,86 @@
assert round(self.browse(cr, uid, ref("product_fifo_icecream")).standard_price) == round(150.0 / 1.2834)
-
Print price
-
!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"))])
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
-
!record {model: stock.picking, id: outgoing_fifo_shipment_neg}:
type: out
-
Picking needs movement from stock
-
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_neg}:
picking_id: outgoing_fifo_shipment_neg
product_id: product_fifo_icecream
product_uom: product.product_uom_kgm
product_qty: 100.0
type: out
-
Let us create another out of 50 kg
-
!record {model: stock.picking, id: outgoing_fifo_shipment_neg2}:
type: out
-
Picking needs movement from stock
-
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_neg2}:
picking_id: outgoing_fifo_shipment_neg2
product_id: product_fifo_icecream
product_uom: product.product_uom_kgm
product_qty: 50.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'})
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'})
self.do_partial(cr, uid, [partial_id])
-
Print price
-
!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"))])
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 200 kgm 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_fifo_neg}:
partner_id: base.res_partner_3
location_id: stock.stock_location_stock
pricelist_id: 1
order_line:
- product_id: product_fifo_icecream
product_qty: 200.0
product_uom: product.product_uom_categ_kgm
price_unit: 50.0
name: 'FIFO Ice Cream'
-
I confirm the first purchase order
-
!workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fifo_neg}
-
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_fifo_neg")).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])
-
Print price
-
!python {model: product.product}: |
print self.browse(cr, uid, ref("product_fifo_icecream")).standard_price

View File

@ -248,13 +248,17 @@ class report_stock_valuation(osv.osv):
'product_qty':fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), readonly=True),
'value' : fields.float('Total Value', digits_compute=dp.get_precision('Account'), required=True),
'move_value' : fields.float('Total Value', digits_compute=dp.get_precision('Account'), required=True),
'inventory_value': fields.float('Inventory Value'),
'state': fields.selection([('draft', 'Draft'), ('waiting', 'Waiting'), ('confirmed', 'Confirmed'), ('assigned', 'Available'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', readonly=True, select=True,
help='When the stock move is created it is in the \'Draft\' state.\n After that it is set to \'Confirmed\' state.\n If stock is available state is set to \'Avaiable\'.\n When the picking it done the state is \'Done\'.\
\nThe state is \'Waiting\' if the move is waiting for another one.'),
'location_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 Type', required=True),
'scrap_location': fields.boolean('scrap'),
'name': fields.text('Name', readonly=True),
'price_unit': fields.float('Unit price', digits_compute=dp.get_precision('Account'))
'price_unit': fields.float('Unit price', digits_compute=dp.get_precision('Account')),
'qty_remaining': fields.float('Qty remaining'),
'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),
}
def init(self, cr):
@ -267,7 +271,7 @@ CREATE OR REPLACE view report_stock_valuation AS (
to_char(m.date, 'MM') as month,
m.partner_id as partner_id, m.location_id as location_id,
m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, l.scrap_location as scrap_location,
m.company_id,
m.company_id, m.qty_remaining as qty_remaining,
m.state as state, m.prodlot_id as prodlot_id,
p.name as name,
CASE WHEN ipcm.value_text in ('fifo','lifo') THEN coalesce(mm.price_unit_out * pu2.factor / pu.factor, 0.0)
@ -275,7 +279,10 @@ CREATE OR REPLACE view report_stock_valuation AS (
coalesce(sum(-ip.value_float * m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as value,
CASE WHEN ipcm.value_text in ('fifo', 'lifo') THEN coalesce(sum(-mm.price_unit_out * mm.qty)::decimal, 0.0)
ELSE coalesce(sum(-m.price_unit * mm.qty)::decimal, 0.0) END as move_value,
coalesce(sum(-mm.qty * pu.factor / pu2.factor)::decimal, 0.0) as product_qty
coalesce(sum(-mm.qty * pu.factor / pu2.factor)::decimal, 0.0) as product_qty,
l_other.usage as location_dest_type,
l.usage as location_src_type,
0.0 as inventory_value
FROM
stock_move_matching mm, stock_move m
LEFT JOIN stock_picking p ON (m.picking_id=p.id)
@ -287,11 +294,12 @@ CREATE OR REPLACE view report_stock_valuation AS (
LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
LEFT JOIN product_uom u ON (m.product_uom=u.id)
LEFT JOIN stock_location l ON (m.location_id=l.id)
LEFT JOIN stock_location l_other ON (m.location_dest_id=l_other.id)
WHERE m.state != 'cancel' and mm.move_out_id=m.id
GROUP BY
p.name, mm.id, mm.move_out_id, m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM'),
pu2.factor, pu.factor, ipcm.value_text
pu2.factor, pu.factor, ipcm.value_text, m.qty_remaining, l_other.usage
) UNION ALL (
SELECT
-min(m.id) as id, m.date as date,
@ -299,27 +307,32 @@ CREATE OR REPLACE view report_stock_valuation AS (
to_char(m.date, 'MM') as month,
m.partner_id as partner_id, m.location_dest_id as location_id,
m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, l.scrap_location as scrap_location,
m.company_id,
m.company_id, m.qty_remaining as qty_remaining,
m.state as state, m.prodlot_id as prodlot_id,
p.name as name, coalesce(m.price_unit * pu2.factor / pu.factor, 0.0) as price_unit,
coalesce(sum(ip.value_float * m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as value,
coalesce(sum(m.price_unit * m.product_qty)::decimal, 0.0) as move_value,
coalesce(sum(m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as product_qty
coalesce(sum(m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as product_qty,
l.usage as location_dest_type,
l_other.usage as location_src_type,
1.0 as inventory_value
FROM
stock_move m
LEFT JOIN stock_picking p ON (m.picking_id=p.id)
LEFT JOIN product_product pp ON (m.product_id=pp.id)
LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.template,',pt.id) AND ip.company_id=m.company_id)
LEFT JOIN ir_property ipcm ON (ipcm.name='cost_method' AND ipcm.res_id=CONCAT('product.tempalte,',pt.id) AND ipcm.company_id=m.company_id)
LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
LEFT JOIN product_uom u ON (m.product_uom=u.id)
LEFT JOIN stock_location l ON (m.location_dest_id=l.id)
LEFT JOIN stock_location l_other ON (m.location_id=l_other.id)
WHERE m.state != 'cancel'
GROUP BY
p.name, m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM'),
pu2.factor, pu.factor
pu2.factor, pu.factor, m.qty_remaining, l_other.usage
)
);
""")

View File

@ -2380,13 +2380,6 @@ class stock_move(osv.osv):
if move.move_dest_id.auto_validate:
self.action_done(cr, uid, [move.move_dest_id.id], context=context)
#TODO: Create stock move matchings if still necessary
# match_obj = self.pool.get("stock.move.matching")
# matches = match_obj.search(cr, uid, [("move_out_id", "=", move.id)], context = context)
# if not matches:
# fifo = not (move.product_id.cost_method == 'lifo')
#matchings = self.pool.get("product.product').get_stock_matchings_fifolifo(cr, uid, [move.product_id.id], move.product_qty, fifo, move.uom_id.id, False)
#for match in matchings:
self._create_product_valuation_moves(cr, uid, move, context=context)
@ -2412,7 +2405,7 @@ class stock_move(osv.osv):
"""
move_list = []
# Consists of access rights
# TODO Check if currency is not needed
# TODO Check if amount_currency is not needed
if type == 'out' and move.product_id.cost_method in ['fifo', 'lifo']:
match_obj = self.pool.get("stock.move.matching")
matches = match_obj.search(cr, uid, [('move_out_id','=', move.id)], context=context)
@ -2700,7 +2693,8 @@ class stock_move(osv.osv):
amount += match[1]
#Write price on out move
if product.qty_available >= product_uom_qty and product.cost_method in ['fifo', 'lifo']:
self.write(cr, uid, move.id, {'price_unit': price_amount / amount}, context=context)
self.write(cr, uid, move.id, {'price_unit': price_amount / amount,
'qty_remaining': move.qty_remaining - amount}, context=context)
product_obj.write(cr, uid, product.id, {'standard_price': price_amount / product_uom_qty}, context=ctx)
else:
new_price = uom_obj._compute_price(cr, uid, product.uom_id.id, product.standard_price,
@ -2716,7 +2710,7 @@ 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)
elif product.cost_method == 'average':
if product_avail[product.id] >= 0.0: #Could put > instead
if product_avail[product.id] >= 0.0: #TODO: Could put > instead
amount_unit = product.standard_price
move_product_price = uom_obj._compute_price(cr, uid, product_uom, move.price_unit, product.uom_id.id)
new_std_price = ((amount_unit * product_avail[product.id])\
@ -2724,9 +2718,43 @@ 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
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):
print "Show how to match for negative stock"
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}
match_id = matching_obj.create(cr, uid, matchvals, context=context)
qty_to_go -= qty
#Need to re-calculate total price of every out_move if FIFO/LIFO
if cost_method in ['fifo', 'lifo']:
matches = matching_obj.search(cr, uid, [('move_out_id', '=', out_mov.id)], context=context)
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)
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
#This could be made optional
#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
new_price = uom_obj._compute_price(cr, uid, move_orig.product_uom, move_orig.price_unit, product.uom_id.id)

View File

@ -58,12 +58,13 @@ class product_product (osv.osv):
currency_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.id
if fifo:
move_in_ids = move_obj.search(cr, uid, [('company_id','=', company_id), ('qty_remaining', '>', 0), ('state', '=', 'done'),
('type', '=', 'in'), ('product_id', '=', product.id)],
('location_id.usage', '!=', 'internal'), ('location_dest_id.usage', '=', 'internal'), ('product_id', '=', product.id)],
order = 'date', context=context)
else:
move_in_ids = move_obj.search(cr, uid, [('company_id','=', company_id), ('qty_remaining', '>', 0), ('state', '=', 'done'),
('type', '=', 'in'), ('product_id', '=', product.id)],
('location_id.usage', '!=', 'internal'), ('location_dest_id.usage', '=', 'internal'), ('product_id', '=', product.id)],
order = 'date desc', context=context)
tuples = []
qty_to_go = qty
for move in move_obj.browse(cr, uid, move_in_ids, context=context):