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/>.
#
##############################################################################
import time
from openerp . osv import fields , osv
from openerp . tools . translate import _
2013-10-15 12:30:28 +00:00
from openerp import SUPERUSER_ID
2013-07-11 13:05:28 +00:00
import logging
_logger = logging . getLogger ( __name__ )
#----------------------------------------------------------
# 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
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
"""
Accounting Valuation Entries
2014-03-11 11:20:47 +00:00
quants : Quants to create accounting valuation entries for
move : Move to use
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 ) :
2013-09-23 14:33:09 +00:00
location_from = move . location_id
2014-03-11 11:20:47 +00:00
location_to = quants [ 0 ] . location_id
2013-07-16 08:06:34 +00:00
if context is None :
context = { }
2014-03-11 11:20:47 +00:00
if quants [ 0 ] . product_id . valuation != ' real_time ' :
2013-07-17 13:26:27 +00:00
return False
2014-03-11 11:20:47 +00:00
if quants [ 0 ] . owner_id :
2013-08-02 09:04:46 +00:00
#if the quant isn't owned by the company, we don't make any valuation entry
return False
2014-03-11 11:20:47 +00:00
if quants [ 0 ] . qty < = 0 :
2013-09-23 14:33:09 +00:00
#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.
2013-07-11 13:05:28 +00:00
return False
2014-03-11 11:20:47 +00:00
company_from = self . _location_owner ( cr , uid , quants [ 0 ] , location_from , context = context )
company_to = self . _location_owner ( cr , uid , quants [ 0 ] , location_to , context = context )
2013-07-11 13:05:28 +00:00
if company_from == company_to :
return False
# Create Journal Entry for products arriving in the company
if 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
if company_from :
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
2013-09-24 14:32:20 +00:00
2014-03-12 11:29:25 +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 = False , context = None ) :
quant = super ( stock_quant , self ) . _quant_create ( cr , uid , qty , move , lot_id , owner_id , src_package_id , dest_package_id , force_location , context = context )
if move . product_id . valuation == ' real_time ' :
self . _account_entry_move ( cr , uid , [ quant ] , move , context )
return quant
2014-03-12 09:45:44 +00:00
def move_single_quant_tuples ( self , cr , uid , quants , move , location_dest_id , dest_package_id , context = None ) :
quant_record = super ( stock_quant , self ) . move_single_quant_tuples ( 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-11 11:20:47 +00:00
quants_filt = [ x [ 0 ] for x in quants ]
self . _account_entry_move ( cr , uid , quants_filt , move , context = context )
2014-03-10 17:54:45 +00:00
return quant_record
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 ' ]
2013-07-11 13:05:28 +00:00
2013-07-12 14:14:07 +00:00
if not all ( [ acc_src , acc_dest , acc_valuation , journal_id ] ) :
2013-07-17 13:26:27 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ''' One of the following information is missing on the product or product category and prevents the accounting valuation entries to be created:
2013-07-12 14:14:07 +00:00
Stock Input Account : % s
Stock Output Account : % s
Stock Valuation Account : % s
Stock Journal : % s
''' ) % (acc_src, acc_dest, acc_valuation, journal_id))
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-03-11 16:57:47 +00:00
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
2014-03-11 15:29:04 +00:00
2013-07-12 14:14:07 +00:00
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 = { }
quant_cost_qty = { }
for quant in quants :
if quant_cost . get ( quant . cost ) :
quant_cost_qty [ quant . cost ] + = quant . qty
else :
quant_cost [ quant . cost ] = quant
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-11 11:20:47 +00:00
for cost in quant_cost_qty . keys ( ) :
2014-03-11 16:57:47 +00:00
move_lines = self . _prepare_account_move_line ( cr , uid , move , quant_cost_qty [ cost ] , cost , credit_account_id , debit_account_id , context = context )
2014-03-11 11:20:47 +00:00
return move_obj . create ( cr , uid , { ' journal_id ' : journal_id ,
' line_id ' : move_lines ,
2014-03-17 10:59:15 +00:00
' perdiod_id ' : self . pool . get ( ' account.period ' ) . find ( cr , uid , move . date , context = context ) [ 0 ] ,
' 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
company_currency_id = move . company_id . currency_id . id
ctx = { ' currency_id ' : company_currency_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 )