[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:
parent
c8d21a6c64
commit
7f046a7b01
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
""")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue