2013-07-11 13:05:28 +00:00
# -*- 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 . osv import fields , osv
from openerp . tools . translate import _
2014-07-06 14:44:26 +00:00
from openerp import SUPERUSER_ID , api
2013-07-11 13:05:28 +00:00
import logging
_logger = logging . getLogger ( __name__ )
2014-04-08 16:01:44 +00:00
class stock_inventory ( osv . osv ) :
_inherit = " stock.inventory "
_columns = {
' period_id ' : fields . many2one ( ' account.period ' , ' Force Valuation Period ' , help = " Choose the accounting period where you want to value the stock moves created by the inventory instead of the default one (chosen by the inventory end date) " ) ,
}
def post_inventory ( self , cr , uid , inv , context = None ) :
if context is None :
context = { }
ctx = context . copy ( )
if inv . period_id :
ctx [ ' force_period ' ] = inv . period_id . id
return super ( stock_inventory , self ) . post_inventory ( cr , uid , inv , context = ctx )
2013-07-11 13:05:28 +00:00
#----------------------------------------------------------
# Stock Location
#----------------------------------------------------------
class stock_location ( osv . osv ) :
_inherit = " stock.location "
_columns = {
2013-09-09 10:02:29 +00:00
' valuation_in_account_id ' : fields . many2one ( ' account.account ' , ' Stock Valuation Account (Incoming) ' , domain = [ ( ' type ' , ' = ' , ' other ' ) ] ,
2013-07-11 13:05:28 +00:00
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. " ) ,
2013-09-09 10:02:29 +00:00
' valuation_out_account_id ' : fields . many2one ( ' account.account ' , ' Stock Valuation Account (Outgoing) ' , domain = [ ( ' type ' , ' = ' , ' other ' ) ] ,
2013-07-11 13:05:28 +00:00
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. "
" This has no effect for internal locations. " ) ,
}
#----------------------------------------------------------
# Quants
#----------------------------------------------------------
class stock_quant ( osv . osv ) :
_inherit = " stock.quant "
2013-12-05 18:10:52 +00:00
def _get_inventory_value ( self , cr , uid , quant , context = None ) :
if quant . product_id . cost_method in ( ' real ' ) :
return quant . cost * quant . qty
return super ( stock_quant , self ) . _get_inventory_value ( cr , uid , quant , context = context )
2013-07-22 16:15:33 +00:00
2014-07-06 14:44:26 +00:00
@api.cr_uid_ids_context
2013-09-23 14:33:09 +00:00
def _price_update ( self , cr , uid , quant_ids , newprice , context = None ) :
2013-09-24 12:49:06 +00:00
''' This function is called at the end of negative quant reconciliation and does the accounting entries adjustemnts and the update of the product cost price if needed
'''
2013-09-23 14:33:09 +00:00
if context is None :
context = { }
2013-09-24 08:28:04 +00:00
super ( stock_quant , self ) . _price_update ( cr , uid , quant_ids , newprice , context = context )
2013-09-23 14:33:09 +00:00
ctx = context . copy ( )
for quant in self . browse ( cr , uid , quant_ids , context = context ) :
move = self . _get_latest_move ( cr , uid , quant , context = context )
# this is where we post accounting entries for adjustment
2013-09-24 12:49:06 +00:00
ctx [ ' force_valuation_amount ' ] = newprice - quant . cost
2014-03-11 11:20:47 +00:00
self . _account_entry_move ( cr , uid , [ quant ] , move , context = ctx )
2013-09-23 14:33:09 +00:00
#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) the product cost's method is 'real'
#2) we just fixed a negative quant caused by an outgoing shipment
2013-09-23 19:42:34 +00:00
if quant . product_id . cost_method == ' real ' and quant . location_id . usage != ' internal ' :
2013-09-23 14:33:09 +00:00
self . pool . get ( ' stock.move ' ) . _store_average_cost_price ( cr , uid , move , context = context )
2013-07-11 13:05:28 +00:00
2014-03-11 11:20:47 +00:00
def _account_entry_move ( self , cr , uid , quants , move , context = None ) :
2014-03-19 16:33:59 +00:00
"""
Accounting Valuation Entries
quants : browse record list of Quants to create accounting valuation entries for . Unempty and all quants are supposed to have the same location id ( thay already moved in )
move : Move to use . browse record
"""
2013-07-16 08:06:34 +00:00
if context is None :
context = { }
2014-03-19 16:33:59 +00:00
location_obj = self . pool . get ( ' stock.location ' )
location_from = move . location_id
2014-04-16 14:53:13 +00:00
location_to = quants [ 0 ] . location_id
2014-03-19 16:33:59 +00:00
company_from = location_obj . _location_owner ( cr , uid , location_from , context = context )
company_to = location_obj . _location_owner ( cr , uid , location_to , context = context )
2013-07-11 13:05:28 +00:00
2014-03-19 16:33:59 +00:00
if move . product_id . valuation != ' real_time ' :
return False
for q in quants :
if q . owner_id :
#if the quant isn't owned by the company, we don't make any valuation entry
return False
if q . qty < = 0 :
#we don't make any stock valuation for negative quants because the valuation is already made for the counterpart.
#At that time the valuation will be made at the product cost price and afterward there will be new accounting entries
#to make the adjustments when we know the real cost price.
return False
2014-04-16 14:53:13 +00:00
#in case of routes making the link between several warehouse of the same company, the transit location belongs to this company, so we don't need to create accounting entries
2013-07-11 13:05:28 +00:00
# Create Journal Entry for products arriving in the company
2014-04-16 14:53:13 +00:00
if company_to and ( move . location_id . usage not in ( ' internal ' , ' transit ' ) and move . location_dest_id . usage == ' internal ' or company_from != company_to ) :
2013-07-16 08:06:34 +00:00
ctx = context . copy ( )
ctx [ ' force_company ' ] = company_to . id
journal_id , acc_src , acc_dest , acc_valuation = self . _get_accounting_data_for_valuation ( cr , uid , move , context = ctx )
2013-07-15 11:22:19 +00:00
if location_from and location_from . usage == ' customer ' :
2013-07-12 14:14:07 +00:00
#goods returned from customer
2014-03-11 11:20:47 +00:00
self . _create_account_move_line ( cr , uid , quants , move , acc_dest , acc_valuation , journal_id , context = ctx )
2013-07-12 14:14:07 +00:00
else :
2014-03-11 11:20:47 +00:00
self . _create_account_move_line ( cr , uid , quants , move , acc_src , acc_valuation , journal_id , context = ctx )
2013-07-11 13:05:28 +00:00
# Create Journal Entry for products leaving the company
2014-04-16 14:53:13 +00:00
if company_from and ( move . location_id . usage == ' internal ' and move . location_dest_id . usage not in ( ' internal ' , ' transit ' ) or company_from != company_to ) :
2013-07-16 08:06:34 +00:00
ctx = context . copy ( )
ctx [ ' force_company ' ] = company_from . id
journal_id , acc_src , acc_dest , acc_valuation = self . _get_accounting_data_for_valuation ( cr , uid , move , context = ctx )
2013-07-15 11:22:19 +00:00
if location_to and location_to . usage == ' supplier ' :
2013-07-12 14:14:07 +00:00
#goods returned to supplier
2014-03-11 11:20:47 +00:00
self . _create_account_move_line ( cr , uid , quants , move , acc_valuation , acc_src , journal_id , context = ctx )
2013-07-12 14:14:07 +00:00
else :
2014-03-11 11:20:47 +00:00
self . _create_account_move_line ( cr , uid , quants , move , acc_valuation , acc_dest , journal_id , context = ctx )
2013-07-11 13:05:28 +00:00
2014-04-16 14:53:13 +00:00
def _quant_create ( self , cr , uid , qty , move , lot_id = False , owner_id = False , src_package_id = False , dest_package_id = False , force_location_from = False , force_location_to = False , context = None ) :
quant = super ( stock_quant , self ) . _quant_create ( cr , uid , qty , move , lot_id = lot_id , owner_id = owner_id , src_package_id = src_package_id , dest_package_id = dest_package_id , force_location_from = force_location_from , force_location_to = force_location_to , context = context )
2014-03-12 11:29:25 +00:00
if move . product_id . valuation == ' real_time ' :
self . _account_entry_move ( cr , uid , [ quant ] , move , context )
return quant
2014-03-19 16:33:59 +00:00
def move_quants_write ( self , cr , uid , quants , move , location_dest_id , dest_package_id , context = None ) :
res = super ( stock_quant , self ) . move_quants_write ( cr , uid , quants , move , location_dest_id , dest_package_id , context = context )
2014-03-10 17:54:45 +00:00
if move . product_id . valuation == ' real_time ' :
2014-03-19 16:33:59 +00:00
self . _account_entry_move ( cr , uid , quants , move , context = context )
return res
2013-07-11 13:05:28 +00:00
2013-09-24 14:32:20 +00:00
2013-07-12 14:14:07 +00:00
def _get_accounting_data_for_valuation ( self , cr , uid , move , context = None ) :
"""
Return the accounts and journal to use to post Journal Entries for the real - time
valuation of the quant .
2013-07-11 13:05:28 +00:00
2013-07-12 14:14:07 +00:00
: param context : context dictionary that can explicitly mention the company to consider via the ' force_company ' key
: returns : journal_id , source account , destination account , valuation account
: raise : osv . except_osv ( ) is any mandatory account or journal is not defined .
"""
2013-07-17 13:26:27 +00:00
product_obj = self . pool . get ( ' product.product ' )
2013-07-12 14:14:07 +00:00
accounts = product_obj . get_product_accounts ( cr , uid , move . product_id . id , context )
if move . location_id . valuation_out_account_id :
acc_src = move . location_id . valuation_out_account_id . id
else :
acc_src = accounts [ ' stock_account_input ' ]
2013-07-11 13:05:28 +00:00
2013-07-12 14:14:07 +00:00
if move . location_dest_id . valuation_in_account_id :
acc_dest = move . location_dest_id . valuation_in_account_id . id
else :
acc_dest = accounts [ ' stock_account_output ' ]
2013-07-11 13:05:28 +00:00
2013-07-12 14:14:07 +00:00
acc_valuation = accounts . get ( ' property_stock_valuation_account_id ' , False )
journal_id = accounts [ ' stock_journal ' ]
return journal_id , acc_src , acc_dest , acc_valuation
2013-07-11 13:05:28 +00:00
2014-03-11 16:57:47 +00:00
def _prepare_account_move_line ( self , cr , uid , move , qty , cost , credit_account_id , debit_account_id , context = None ) :
2013-07-12 14:14:07 +00:00
"""
Generate the account . move . line values to post to track the stock valuation difference due to the
processing of the given quant .
"""
2013-09-23 14:33:09 +00:00
if context is None :
context = { }
2013-11-04 16:49:39 +00:00
currency_obj = self . pool . get ( ' res.currency ' )
2013-09-23 14:33:09 +00:00
if context . get ( ' force_valuation_amount ' ) :
valuation_amount = context . get ( ' force_valuation_amount ' )
else :
2014-08-05 08:08:52 +00:00
if move . product_id . cost_method == ' average ' :
valuation_amount = move . location_id . usage != ' internal ' and move . location_dest_id . usage == ' internal ' and cost or move . product_id . standard_price
else :
valuation_amount = move . product_id . cost_method == ' real ' and cost or move . product_id . standard_price
2013-11-04 16:49:39 +00:00
#the standard_price of the product may be in another decimal precision, or not compatible with the coinage of
#the company currency... so we need to use round() before creating the accounting entries.
2014-03-11 16:57:47 +00:00
valuation_amount = currency_obj . round ( cr , uid , move . company_id . currency_id , valuation_amount * qty )
2013-07-12 14:14:07 +00:00
partner_id = ( move . picking_id . partner_id and self . pool . get ( ' res.partner ' ) . _find_accounting_partner ( move . picking_id . partner_id ) . id ) or False
debit_line_vals = {
' name ' : move . name ,
2014-03-11 16:57:47 +00:00
' product_id ' : move . product_id . id ,
2014-03-11 11:20:47 +00:00
' quantity ' : qty ,
2014-03-11 16:57:47 +00:00
' product_uom_id ' : move . product_id . uom_id . id ,
2013-07-12 14:14:07 +00:00
' ref ' : move . picking_id and move . picking_id . name or False ,
2014-03-14 13:59:38 +00:00
' date ' : move . date ,
2013-07-12 14:14:07 +00:00
' partner_id ' : partner_id ,
2013-11-04 16:49:39 +00:00
' debit ' : valuation_amount > 0 and valuation_amount or 0 ,
' credit ' : valuation_amount < 0 and - valuation_amount or 0 ,
2013-07-12 14:14:07 +00:00
' account_id ' : debit_account_id ,
}
credit_line_vals = {
' name ' : move . name ,
2014-03-11 16:57:47 +00:00
' product_id ' : move . product_id . id ,
2014-03-11 11:20:47 +00:00
' quantity ' : qty ,
2014-03-11 16:57:47 +00:00
' product_uom_id ' : move . product_id . uom_id . id ,
2013-07-12 14:14:07 +00:00
' ref ' : move . picking_id and move . picking_id . name or False ,
2014-03-14 13:59:38 +00:00
' date ' : move . date ,
2013-07-12 14:14:07 +00:00
' partner_id ' : partner_id ,
2013-11-04 16:49:39 +00:00
' credit ' : valuation_amount > 0 and valuation_amount or 0 ,
' debit ' : valuation_amount < 0 and - valuation_amount or 0 ,
2013-07-12 14:14:07 +00:00
' account_id ' : credit_account_id ,
}
2013-11-04 16:49:39 +00:00
return [ ( 0 , 0 , debit_line_vals ) , ( 0 , 0 , credit_line_vals ) ]
2013-07-11 13:05:28 +00:00
2014-03-11 11:20:47 +00:00
def _create_account_move_line ( self , cr , uid , quants , move , credit_account_id , debit_account_id , journal_id , context = None ) :
#group quants by cost
quant_cost_qty = { }
for quant in quants :
2014-03-19 16:33:59 +00:00
if quant_cost_qty . get ( quant . cost ) :
2014-03-11 11:20:47 +00:00
quant_cost_qty [ quant . cost ] + = quant . qty
else :
2014-03-11 15:29:04 +00:00
quant_cost_qty [ quant . cost ] = quant . qty
2013-07-16 08:06:34 +00:00
move_obj = self . pool . get ( ' account.move ' )
2014-03-19 16:33:59 +00:00
for cost , qty in quant_cost_qty . items ( ) :
move_lines = self . _prepare_account_move_line ( cr , uid , move , qty , cost , credit_account_id , debit_account_id , context = context )
2014-04-08 16:01:44 +00:00
period_id = context . get ( ' force_period ' , self . pool . get ( ' account.period ' ) . find ( cr , uid , move . date , context = context ) [ 0 ] )
2014-04-10 14:33:47 +00:00
move_obj . create ( cr , uid , { ' journal_id ' : journal_id ,
2014-03-11 11:20:47 +00:00
' line_id ' : move_lines ,
2014-04-08 16:01:44 +00:00
' period_id ' : period_id ,
2014-03-17 10:59:15 +00:00
' date ' : move . date ,
2014-03-11 11:20:47 +00:00
' ref ' : move . picking_id and move . picking_id . name } , context = context )
2013-09-09 10:02:29 +00:00
2013-09-23 14:33:09 +00:00
#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_solving_quant, 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)
# return remaining_solving_quant, remaining_to_solve_quant
2013-09-09 10:02:29 +00:00
2013-09-03 16:27:57 +00:00
class stock_move ( osv . osv ) :
_inherit = " stock.move "
2013-11-18 09:50:21 +00:00
def action_done ( self , cr , uid , ids , context = None ) :
2013-09-09 15:47:54 +00:00
self . product_price_update_before_done ( cr , uid , ids , context = context )
2013-11-18 09:50:21 +00:00
super ( stock_move , self ) . action_done ( cr , uid , ids , context = context )
2013-09-09 15:47:54 +00:00
self . product_price_update_after_done ( cr , uid , ids , context = context )
2013-07-16 08:06:34 +00:00
2013-09-09 10:02:29 +00:00
def _store_average_cost_price ( self , cr , uid , move , context = None ) :
''' move is a browe record '''
product_obj = self . pool . get ( ' product.product ' )
2013-09-09 15:47:54 +00:00
move . refresh ( )
2013-09-09 10:02:29 +00:00
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
2013-10-15 12:30:28 +00:00
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
2013-12-05 10:05:16 +00:00
product_obj . write ( cr , SUPERUSER_ID , [ move . product_id . id ] , { ' standard_price ' : average_valuation_price } , context = context )
self . write ( cr , uid , [ move . id ] , { ' price_unit ' : average_valuation_price } , context = context )
2013-09-09 10:02:29 +00:00
2013-09-09 15:47:54 +00:00
def product_price_update_before_done ( self , cr , uid , ids , context = None ) :
2013-09-03 16:27:57 +00:00
product_obj = self . pool . get ( ' product.product ' )
for move in self . browse ( cr , uid , ids , context = context ) :
2013-09-05 10:34:35 +00:00
#adapt standard price on incomming moves if the product cost_method is 'average'
if ( move . location_id . usage == ' supplier ' ) and ( move . product_id . cost_method == ' average ' ) :
product = move . product_id
product_avail = product . qty_available
if product . qty_available < = 0 :
new_std_price = move . price_unit
else :
# Get the standard price
2013-11-04 16:49:39 +00:00
amount_unit = product . standard_price
2013-09-09 15:47:54 +00:00
new_std_price = ( ( amount_unit * product_avail ) + ( move . price_unit * move . product_qty ) ) / ( product_avail + move . product_qty )
2013-10-15 12:30:28 +00:00
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
product_obj . write ( cr , SUPERUSER_ID , [ product . id ] , { ' standard_price ' : new_std_price } , context = context )
2013-09-05 10:34:35 +00:00
2013-09-09 15:47:54 +00:00
def product_price_update_after_done ( self , cr , uid , ids , context = None ) :
'''
This method adapts the price on the product when necessary
'''
for move in self . browse ( cr , uid , ids , context = context ) :
2013-09-05 10:34:35 +00:00
#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.
2013-09-03 16:27:57 +00:00
if move . product_id . cost_method == ' real ' and move . location_dest_id . usage != ' internal ' :
2013-09-09 10:02:29 +00:00
#store the average price of the move on the move and product form
self . _store_average_cost_price ( cr , uid , move , context = context )