[FIX] stock: update the standard price of product with cost_method == 'real' on negative quant reconciliation.

bzr revid: qdp-launchpad@openerp.com-20130909100229-txuyflkp6iikiesq
This commit is contained in:
Quentin (OpenERP) 2013-09-09 12:02:29 +02:00
parent 6c9490d11c
commit 5d8cd42332
3 changed files with 62 additions and 47 deletions

View File

@ -255,9 +255,11 @@
Check rounded price is 150.0 / 1.2834
-
!python {model: product.product}: |
assert round(self.browse(cr, uid, ref("product_fifo_icecream")).standard_price) == round(150.0 / 1.2834), "Product price not updated accordingly. %s found instead of %s" %(self.browse(cr, uid, ref("product_fifo_icecream")).standard_price, round(150.0/1.2834))
product = self.browse(cr, uid, ref("product_fifo_icecream"))
assert round(product.standard_price) == round(150.0 / 1.2834), "Product price not updated accordingly. %s found instead of %s" %(product.standard_price, round(150.0/1.2834))
assert product.qty_available == 0.0, 'Wrong quantity in stock after first reception'
-
Let us create some outs to get negative stock. Create outpicking. We create delivery order of 200 kg, but will pick only 100 kg
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}:
picking_type_id: stock.picking_type_out
@ -268,12 +270,18 @@
picking_id: outgoing_fifo_shipment_neg
product_id: product_fifo_icecream
product_uom: product.product_uom_kgm
product_uom_qty: 200.0
product_uom_qty: 100.0
location_id: stock.stock_location_stock
location_dest_id: stock.stock_location_customers
picking_type_id: stock.picking_type_out
-
Let us create another out of 400 kg, but will pick only 50 kg
Process the delivery of the first outgoing shipment
-
!python {model: stock.picking}: |
picking_obj = self.browse(cr, uid, ref("outgoing_fifo_shipment_neg"))
picking_obj.do_partial(context=context)
-
Let us create another out of 400 kg
-
!record {model: stock.picking, id: outgoing_fifo_shipment_neg2}:
picking_type_id: stock.picking_type_out
@ -292,8 +300,6 @@
Process the delivery of the outgoing shipments
-
!python {model: stock.picking}: |
picking_obj = self.browse(cr, uid, ref("outgoing_fifo_shipment_neg"))
picking_obj.do_partial(context=context)
picking_obj1 = self.browse(cr, uid, ref("outgoing_fifo_shipment_neg2"))
picking_obj1.do_partial(context=context)
-

View File

@ -167,6 +167,7 @@ class stock_quant(osv.osv):
# Used for negative quants to reconcile after compensated by a new positive one
'propagated_from_id': fields.many2one('stock.quant', 'Linked Quant', help='The negative quant this is coming from'),
'negative_dest_location_id': fields.many2one('stock.location', 'Destination Location', help='Technical field used to record the destination location of a move that created a negative quant'),
}
_defaults = {
@ -260,6 +261,7 @@ class stock_quant(osv.osv):
negative_vals['location_id'] = move.location_id.id
negative_vals['qty'] = -qty
negative_vals['cost'] = price_unit
negative_vals['negative_dest_location_id'] = move.location_dest_id.id
negative_quant_id = self.create(cr, uid, negative_vals, context=context)
vals.update({'propagated_from_id': negative_quant_id})
@ -283,6 +285,20 @@ class stock_quant(osv.osv):
move = m
return move
def _reconcile_single_negative_quant(self, cr, uid, to_solve_quant, quant, quant_neg, qty, context=None):
move = self._get_latest_move(cr, uid, to_solve_quant, context=context)
self._quant_split(cr, uid, quant, qty, context=context)
remaining_to_solve_quant = self._quant_split(cr, uid, to_solve_quant, qty, context=context)
remaining_neg_quant = self._quant_split(cr, uid, quant_neg, -qty, context=context)
#if the reconciliation was not complete, we need to link together the remaining parts
if remaining_to_solve_quant and remaining_neg_quant:
self.write(cr, uid, remaining_to_solve_quant.id, {'propagated_from_id': remaining_neg_quant.id}, context=context)
#delete the reconciled quants, as it is replaced by the solving quant
self.unlink(cr, SUPERUSER_ID, [quant_neg.id, to_solve_quant.id], context=context)
#call move_single_quant to ensure recursivity if necessary and do the stock valuation
self.move_single_quant(cr, uid, quant, qty, move, context=context)
return remaining_to_solve_quant
def _quant_reconcile_negative(self, cr, uid, quant, context=None):
"""
When new quant arrive in a location, try to reconcile it with
@ -292,27 +308,14 @@ class stock_quant(osv.osv):
if quant.location_id.usage != 'internal':
return False
quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, [('qty', '<', '0')], context=context)
result = False
for quant_neg, qty in quants:
if not quant_neg:
continue
result = True
to_solve_quant = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', '!=', quant.id)], context=context)
if not to_solve_quant:
continue
to_solve_quant = self.browse(cr, uid, to_solve_quant[0], context=context)
move = self._get_latest_move(cr, uid, to_solve_quant, context=context)
self._quant_split(cr, uid, quant, qty, context=context)
remaining_to_solve_quant = self._quant_split(cr, uid, to_solve_quant, qty, context=context)
remaining_neg_quant = self._quant_split(cr, uid, quant_neg, -qty, context=context)
#if the reconciliation was not complete, we need to link together the remaining parts
if remaining_to_solve_quant and remaining_neg_quant:
self.write(cr, uid, remaining_to_solve_quant.id, {'propagated_from_id': remaining_neg_quant.id}, context=context)
#delete the reconciled quants, as it is replaced by the solving quant
self.unlink(cr, SUPERUSER_ID, [quant_neg.id, to_solve_quant.id], context=context)
#call move_single_quant to ensure recursivity if necessary and do the stock valuation
self.move_single_quant(cr, uid, quant, qty, move, context=context)
return result
self._reconcile_single_negative_quant(cr, uid, to_solve_quant, quant, quant_neg, qty, context=context)
def _price_update(self, cr, uid, quant, newprice, context=None):
self.write(cr, uid, [quant.id], {'cost': newprice}, context=context)
@ -1508,7 +1511,7 @@ class stock_move(osv.osv):
# quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, context=context)
# quant_obj.quants_move(cr, uid, quants, move, location_dest_id, context=context)
# should replace the above 2 lines
domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id)]
domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id), ('qty', '>', 0)]
prefered_order = 'reservation_id'
# if lot_id:
# prefered_order = 'lot_id<>' + lot_id + ", " + prefered_order

View File

@ -19,23 +19,13 @@
#
##############################################################################
from datetime import datetime
from dateutil.relativedelta import relativedelta
import time
from operator import itemgetter
from itertools import groupby
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp import netsvc
from openerp import tools
from openerp.tools import float_compare, DEFAULT_SERVER_DATETIME_FORMAT
import openerp.addons.decimal_precision as dp
import logging
_logger = logging.getLogger(__name__)
#----------------------------------------------------------
# Stock Location
#----------------------------------------------------------
@ -44,12 +34,12 @@ class stock_location(osv.osv):
_inherit = "stock.location"
_columns = {
'valuation_in_account_id': fields.many2one('account.account', 'Stock Valuation Account (Incoming)', domain = [('type','=','other')],
'valuation_in_account_id': fields.many2one('account.account', 'Stock Valuation Account (Incoming)', domain=[('type', '=', 'other')],
help="Used for real-time inventory valuation. When set on a virtual location (non internal type), "
"this account will be used to hold the value of products being moved from an internal location "
"into this location, instead of the generic Stock Output Account set on the product. "
"This has no effect for internal locations."),
'valuation_out_account_id': fields.many2one('account.account', 'Stock Valuation Account (Outgoing)', domain = [('type','=','other')],
'valuation_out_account_id': fields.many2one('account.account', 'Stock Valuation Account (Outgoing)', domain=[('type', '=', 'other')],
help="Used for real-time inventory valuation. When set on a virtual location (non internal type), "
"this account will be used to hold the value of products being moved out of this location "
"and into an internal location, instead of the generic Stock Output Account set on the product. "
@ -63,7 +53,6 @@ class stock_location(osv.osv):
class stock_quant(osv.osv):
_inherit = "stock.quant"
def _get_inventory_value(self, cr, uid, line, prodbrow, context=None):
#TODO: what in case of partner_id
if prodbrow[(line.company_id.id, line.product_id.id)].cost_method in ('real'):
@ -170,7 +159,7 @@ class stock_quant(osv.osv):
'name': move.name,
'product_id': quant.product_id.id,
'quantity': quant.qty,
'product_uom_id': quant.product_id.uom_id.id,
'product_uom_id': quant.product_id.uom_id.id,
'ref': move.picking_id and move.picking_id.name or False,
'date': time.strftime('%Y-%m-%d'),
'partner_id': partner_id,
@ -181,7 +170,7 @@ class stock_quant(osv.osv):
'name': move.name,
'product_id': quant.product_id.id,
'quantity': quant.qty,
'product_uom_id': quant.product_id.uom_id.id,
'product_uom_id': quant.product_id.uom_id.id,
'ref': move.picking_id and move.picking_id.name or False,
'date': time.strftime('%Y-%m-%d'),
'partner_id': partner_id,
@ -197,6 +186,18 @@ class stock_quant(osv.osv):
return move_obj.create(cr, uid, {'journal_id': journal_id,
'line_id': move_lines,
'ref': move.picking_id and move.picking_id.name}, context=context)
def _reconcile_single_negative_quant(self, cr, uid, to_solve_quant, quant, quant_neg, qty, context=None):
move = self._get_latest_move(cr, uid, to_solve_quant, context=context)
quant_neg_position = quant_neg.negative_dest_location_id.usage
remaining_to_solve_quant = super(stock_quant, self)._reconcile_single_negative_quant(cr, uid, to_solve_quant, quant, quant_neg, qty, context=context)
#update the standard price of the product, only if we would have done it if we'd have had enough stock at first, which means
#1) there isn't any negative quant anymore
#2) the product cost's method is 'real'
#3) we just fixed a negative quant caused by an outgoing shipment
if not remaining_to_solve_quant and move.product_id.cost_method == 'real' and quant_neg_position != 'internal':
self.pool.get('stock.move')._store_average_cost_price(cr, uid, move, context=context)
class stock_move(osv.osv):
_inherit = "stock.move"
@ -204,6 +205,20 @@ class stock_move(osv.osv):
super(stock_move, self).action_done(cr, uid, ids, context=context)
self.product_price_update(cr, uid, ids, context=context)
def _store_average_cost_price(self, cr, uid, move, context=None):
''' move is a browe record '''
product_obj = self.pool.get('product.product')
if any([q.qty <= 0 for q in move.quant_ids]):
#if there is a negative quant, the standard price shouldn't be updated
return
#Note: here we can't store a quant.cost directly as we may have moved out 2 units (1 unit to 5€ and 1 unit to 7€) and in case of a product return of 1 unit, we can't know which of the 2 costs has to be used (5€ or 7€?). So at that time, thanks to the average valuation price we are storing we will svaluate it at 6€
average_valuation_price = 0.0
for q in move.quant_ids:
average_valuation_price += q.qty * q.cost
average_valuation_price = average_valuation_price / move.product_qty
product_obj.write(cr, uid, move.product_id.id, {'standard_price': average_valuation_price}, context=context)
self.write(cr, uid, move.id, {'price_unit': average_valuation_price}, context=context)
def product_price_update(self, cr, uid, ids, context=None):
'''
This method adapts the price on the product when necessary
@ -228,14 +243,5 @@ class stock_move(osv.osv):
#adapt standard price on outgoing moves if the product cost_method is 'real', so that a return
#or an inventory loss is made using the last value used for an outgoing valuation.
if move.product_id.cost_method == 'real' and move.location_dest_id.usage != 'internal':
if any([q.qty <= 0 for q in move.quant_ids]):
#if there is a negative quant, the standard price shouldn't be updated
continue
#get the average price of the move
#Note: here we can't use the quant.cost directly as we may have moved out 2 units (1 unit to 5€ and 1 unit to 7€) and in case of a product return of 1 unit, we can't know which of the 2 cost has to be used (5€ or 7€?). So at that time, thanks to the average valuation price we are storing we will svaluate it at 6€
average_valuation_price = 0.0
for q in move.quant_ids:
average_valuation_price += q.qty * q.cost
average_valuation_price = average_valuation_price / move.product_qty
product_obj.write(cr, uid, move.product_id.id, {'standard_price': average_valuation_price}, context=context)
self.write(cr, uid, move.id, {'price_unit': average_valuation_price}, context=context)
#store the average price of the move on the move and product form
self._store_average_cost_price(cr, uid, move, context=context)