[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:
parent
6c9490d11c
commit
5d8cd42332
|
@ -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)
|
||||
-
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue