2014-04-08 11:39:35 +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
import openerp . addons . decimal_precision as dp
2014-04-11 12:54:30 +00:00
from openerp . tools . translate import _
2014-04-08 11:39:35 +00:00
import product
2014-04-24 13:35:18 +00:00
2014-04-08 11:39:35 +00:00
class stock_landed_cost ( osv . osv ) :
_name = ' stock.landed.cost '
_description = ' Stock Landed Cost '
2014-04-24 10:03:37 +00:00
_inherit = ' mail.thread '
_track = {
' state ' : {
2014-04-24 13:20:20 +00:00
' stock_landed_costs.mt_stock_landed_cost_open ' : lambda self , cr , uid , obj , ctx = None : obj [ ' state ' ] == ' done ' ,
2014-04-24 10:03:37 +00:00
} ,
}
2014-04-08 11:39:35 +00:00
def _total_amount ( self , cr , uid , ids , name , args , context = None ) :
result = { }
for cost in self . browse ( cr , uid , ids , context = context ) :
total = 0.0
for line in cost . cost_lines :
2014-04-09 12:45:22 +00:00
total + = line . price_unit
2014-04-08 11:39:35 +00:00
result [ cost . id ] = total
return result
def _get_cost_line ( self , cr , uid , ids , context = None ) :
2014-04-24 13:35:18 +00:00
cost_to_recompute = [ ]
2014-04-08 11:39:35 +00:00
for line in self . pool . get ( ' stock.landed.cost.lines ' ) . browse ( cr , uid , ids , context = context ) :
2014-04-24 13:35:18 +00:00
cost_to_recompute . append ( line . cost_id . id )
return cost_to_recompute
2014-04-08 11:39:35 +00:00
2014-08-08 08:58:14 +00:00
def get_valuation_lines ( self , cr , uid , ids , picking_ids = None , context = None ) :
2014-04-09 09:25:51 +00:00
picking_obj = self . pool . get ( ' stock.picking ' )
lines = [ ]
if not picking_ids :
2014-08-08 08:58:14 +00:00
return lines
2014-04-09 09:25:51 +00:00
for picking in picking_obj . browse ( cr , uid , picking_ids ) :
for move in picking . move_lines :
2014-04-24 09:19:05 +00:00
#it doesn't make sense to make a landed cost for a product that isn't set as being valuated in real time at real cost
if move . product_id . valuation != ' real_time ' or move . product_id . cost_method != ' real ' :
continue
2014-04-10 09:49:33 +00:00
total_cost = 0.0
2014-04-24 13:35:18 +00:00
total_qty = move . product_qty
2014-04-24 13:15:10 +00:00
weight = move . product_id and move . product_id . weight * move . product_qty
volume = move . product_id and move . product_id . volume * move . product_qty
2014-04-10 09:49:33 +00:00
for quant in move . quant_ids :
total_cost + = quant . cost
2014-08-08 08:58:14 +00:00
vals = dict ( product_id = move . product_id . id , move_id = move . id , quantity = move . product_uom_qty , former_cost = total_cost * total_qty , weight = weight , volume = volume )
2014-04-09 09:25:51 +00:00
lines . append ( vals )
2014-08-08 08:58:14 +00:00
if not lines :
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' The selected picking does not contain any move that would be impacted by landed costs. Landed costs are only possible for products configured in real time valuation with real price costing method. Please make sure it is the case, or you selected the correct picking ' ) )
return lines
2014-04-09 09:25:51 +00:00
2014-04-08 11:39:35 +00:00
_columns = {
2014-07-06 14:44:26 +00:00
' name ' : fields . char ( ' Name ' , track_visibility = ' always ' , readonly = True , copy = False ) ,
' date ' : fields . date ( ' Date ' , required = True , states = { ' done ' : [ ( ' readonly ' , True ) ] } , track_visibility = ' onchange ' , copy = False ) ,
' picking_ids ' : fields . many2many ( ' stock.picking ' , string = ' Pickings ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } , copy = False ) ,
' cost_lines ' : fields . one2many ( ' stock.landed.cost.lines ' , ' cost_id ' , ' Cost Lines ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } , copy = True ) ,
2014-04-24 10:37:52 +00:00
' valuation_adjustment_lines ' : fields . one2many ( ' stock.valuation.adjustment.lines ' , ' cost_id ' , ' Valuation Adjustments ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
' description ' : fields . text ( ' Item Description ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2014-04-08 11:39:35 +00:00
' amount_total ' : fields . function ( _total_amount , type = ' float ' , string = ' Total ' , digits_compute = dp . get_precision ( ' Account ' ) ,
store = {
' stock.landed.cost ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' cost_lines ' ] , 20 ) ,
' stock.landed.cost.lines ' : ( _get_cost_line , [ ' price_unit ' , ' quantity ' , ' cost_id ' ] , 20 ) ,
2014-04-24 10:03:37 +00:00
} , track_visibility = ' always '
2014-04-08 11:39:35 +00:00
) ,
2014-07-06 14:44:26 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' Draft ' ) , ( ' done ' , ' Posted ' ) , ( ' cancel ' , ' Cancelled ' ) ] , ' State ' , readonly = True , track_visibility = ' onchange ' , copy = False ) ,
' account_move_id ' : fields . many2one ( ' account.move ' , ' Journal Entry ' , readonly = True , copy = False ) ,
2014-04-24 10:37:52 +00:00
' account_journal_id ' : fields . many2one ( ' account.journal ' , ' Account Journal ' , required = True ) ,
2014-04-08 11:39:35 +00:00
}
_defaults = {
2014-04-25 10:49:45 +00:00
' name ' : lambda obj , cr , uid , context : obj . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.landed.cost ' ) ,
2014-04-08 11:39:35 +00:00
' state ' : ' draft ' ,
2014-04-24 13:35:18 +00:00
' date ' : fields . date . context_today ,
2014-04-08 11:39:35 +00:00
}
2014-04-24 10:37:52 +00:00
def _create_accounting_entries ( self , cr , uid , line , move_id , context = None ) :
product_obj = self . pool . get ( ' product.product ' )
cost_product = line . cost_line_id and line . cost_line_id . product_id
if not cost_product :
return False
accounts = product_obj . get_product_accounts ( cr , uid , line . product_id . id , context = context )
2014-04-25 08:40:06 +00:00
debit_account_id = accounts [ ' property_stock_valuation_account_id ' ]
credit_account_id = cost_product . property_account_expense and cost_product . property_account_expense . id or cost_product . categ_id . property_account_expense_categ . id
if not credit_account_id :
2014-04-24 10:37:52 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' Please configure Stock Expense Account for product: %s . ' ) % ( cost_product . name ) )
return self . _create_account_move_line ( cr , uid , line , move_id , credit_account_id , debit_account_id , context = context )
def _create_account_move_line ( self , cr , uid , line , move_id , credit_account_id , debit_account_id , context = None ) :
2014-04-11 12:54:30 +00:00
"""
Generate the account . move . line values to track the landed cost .
"""
2014-04-24 10:37:52 +00:00
aml_obj = self . pool . get ( ' account.move.line ' )
aml_obj . create ( cr , uid , {
' name ' : line . name ,
' move_id ' : move_id ,
2014-04-11 12:54:30 +00:00
' product_id ' : line . product_id . id ,
' quantity ' : line . quantity ,
' debit ' : line . additional_landed_cost ,
' account_id ' : debit_account_id
2014-04-24 10:37:52 +00:00
} , context = context )
aml_obj . create ( cr , uid , {
' name ' : line . name ,
' move_id ' : move_id ,
2014-04-11 12:54:30 +00:00
' product_id ' : line . product_id . id ,
' quantity ' : line . quantity ,
' credit ' : line . additional_landed_cost ,
' account_id ' : credit_account_id
2014-04-24 10:37:52 +00:00
} , context = context )
return True
2014-04-11 12:54:30 +00:00
2014-04-24 10:37:52 +00:00
def _create_account_move ( self , cr , uid , cost , context = None ) :
2014-04-11 12:54:30 +00:00
vals = {
2014-04-24 10:37:52 +00:00
' journal_id ' : cost . account_journal_id . id ,
2014-04-11 12:54:30 +00:00
' period_id ' : self . pool . get ( ' account.period ' ) . find ( cr , uid , cost . date , context = context ) [ 0 ] ,
' date ' : cost . date ,
' ref ' : cost . name
}
2014-04-24 10:37:52 +00:00
return self . pool . get ( ' account.move ' ) . create ( cr , uid , vals , context = context )
2014-04-11 12:54:30 +00:00
2014-04-24 10:37:52 +00:00
def button_validate ( self , cr , uid , ids , context = None ) :
2014-04-10 09:49:33 +00:00
quant_obj = self . pool . get ( ' stock.quant ' )
for cost in self . browse ( cr , uid , ids , context = context ) :
2014-04-24 12:16:56 +00:00
if not cost . valuation_adjustment_lines :
2014-04-24 13:08:46 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' You cannot validate a landed cost which has no valuation line. ' ) )
2014-04-24 10:37:52 +00:00
move_id = self . _create_account_move ( cr , uid , cost , context = context )
2014-04-24 10:24:31 +00:00
quant_dict = { }
2014-04-10 09:49:33 +00:00
for line in cost . valuation_adjustment_lines :
2014-04-24 13:08:46 +00:00
if not line . move_id :
continue
2014-04-10 09:49:33 +00:00
per_unit = line . final_cost / line . quantity
diff = per_unit - line . former_cost_per_unit
2014-04-24 13:08:46 +00:00
quants = [ quant for quant in line . move_id . quant_ids ]
2014-04-10 09:49:33 +00:00
for quant in quants :
2014-04-24 10:24:31 +00:00
if quant . id not in quant_dict :
quant_dict [ quant . id ] = quant . cost + diff
2014-04-10 09:49:33 +00:00
else :
2014-04-24 10:24:31 +00:00
quant_dict [ quant . id ] + = diff
2014-04-24 10:37:52 +00:00
for key , value in quant_dict . items ( ) :
quant_obj . write ( cr , uid , quant . id , { ' cost ' : value } , context = context )
self . _create_accounting_entries ( cr , uid , line , move_id , context = context )
self . write ( cr , uid , cost . id , { ' state ' : ' done ' , ' account_move_id ' : move_id } , context = context )
2014-04-24 13:30:30 +00:00
return True
def button_cancel ( self , cr , uid , ids , context = None ) :
self . write ( cr , uid , ids , { ' state ' : ' cancel ' } , context = context )
2014-04-08 11:39:35 +00:00
return True
2014-04-09 13:11:55 +00:00
def compute_landed_cost ( self , cr , uid , ids , context = None ) :
2014-04-10 05:21:46 +00:00
line_obj = self . pool . get ( ' stock.valuation.adjustment.lines ' )
2014-08-08 08:58:14 +00:00
unlink_ids = line_obj . search ( cr , uid , [ ( ' cost_id ' , ' in ' , ids ) ] , context = context )
line_obj . unlink ( cr , uid , unlink_ids , context = context )
towrite_dict = { }
2014-04-10 05:21:46 +00:00
for cost in self . browse ( cr , uid , ids , context = None ) :
2014-08-08 08:58:14 +00:00
if not cost . picking_ids :
continue
picking_ids = [ p . id for p in cost . picking_ids ]
2014-04-10 05:21:46 +00:00
total_qty = 0.0
total_cost = 0.0
2014-04-11 09:39:57 +00:00
total_weight = 0.0
total_volume = 0.0
2014-04-10 05:21:46 +00:00
total_line = 0.0
2014-04-11 09:39:57 +00:00
for line in cost . cost_lines :
2014-08-08 08:58:14 +00:00
vals = self . get_valuation_lines ( cr , uid , [ cost . id ] , picking_ids = picking_ids , context = context )
for v in vals :
v . update ( { ' cost_id ' : cost . id , ' cost_line_id ' : line . id } )
self . pool . get ( ' stock.valuation.adjustment.lines ' ) . create ( cr , uid , v , context = context )
if line . split_method == ' by_quantity ' :
total_qty + = v . get ( ' quantity ' , 0.0 )
elif line . split_method == ' by_current_cost_price ' :
total_cost + = v . get ( ' former_cost ' , 0.0 )
elif line . split_method == ' by_weight ' :
total_weight + = v . get ( ' weight ' , 0.0 )
elif line . split_method == ' by_volume ' :
total_volume + = v . get ( ' volume ' , 0.0 )
else :
total_line + = 1
2014-04-10 05:21:46 +00:00
for line in cost . cost_lines :
for valuation in cost . valuation_adjustment_lines :
2014-04-21 10:41:49 +00:00
value = 0.0
2014-04-11 09:39:57 +00:00
if valuation . cost_line_id and valuation . cost_line_id . id == line . id :
2014-04-21 10:41:49 +00:00
if line . split_method == ' by_quantity ' and total_qty :
2014-04-11 09:39:57 +00:00
per_unit = ( line . price_unit / total_qty )
value = valuation . quantity * per_unit
2014-04-21 10:41:49 +00:00
elif line . split_method == ' by_weight ' and total_weight :
per_unit = ( line . price_unit / total_weight )
value = valuation . weight * per_unit
elif line . split_method == ' by_volume ' and total_volume :
per_unit = ( line . price_unit / total_volume )
value = valuation . volume * per_unit
2014-04-11 09:39:57 +00:00
elif line . split_method == ' equal ' :
2014-04-21 10:41:49 +00:00
value = ( line . price_unit / total_line )
elif line . split_method == ' by_current_cost_price ' and total_cost :
2014-04-11 09:39:57 +00:00
per_unit = ( line . price_unit / total_cost )
value = valuation . former_cost * per_unit
2014-04-21 10:41:49 +00:00
else :
value = ( line . price_unit / total_line )
2014-08-08 08:58:14 +00:00
if valuation . id not in towrite_dict :
towrite_dict [ valuation . id ] = value
2014-04-21 10:41:49 +00:00
else :
2014-08-08 08:58:14 +00:00
towrite_dict [ valuation . id ] + = value
if towrite_dict :
for key , value in towrite_dict . items ( ) :
line_obj . write ( cr , uid , key , { ' additional_landed_cost ' : value } , context = context )
2014-04-09 13:11:55 +00:00
return True
2014-04-24 13:35:18 +00:00
2014-04-08 11:39:35 +00:00
class stock_landed_cost_lines ( osv . osv ) :
_name = ' stock.landed.cost.lines '
_description = ' Stock Landed Cost Lines '
2014-04-10 10:28:25 +00:00
def onchange_product_id ( self , cr , uid , ids , product_id = False , context = None ) :
2014-04-08 11:39:35 +00:00
result = { }
if not product_id :
2014-04-10 05:30:41 +00:00
return { ' value ' : { ' quantity ' : 0.0 , ' price_unit ' : 0.0 } }
2014-04-08 11:39:35 +00:00
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
result [ ' name ' ] = product . name
result [ ' split_method ' ] = product . split_method
result [ ' price_unit ' ] = product . standard_price
2014-04-25 08:40:06 +00:00
result [ ' account_id ' ] = product . property_account_expense and product . property_account_expense . id or product . categ_id . property_account_expense_categ . id
2014-04-08 11:39:35 +00:00
return { ' value ' : result }
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Description ' ) ,
2014-04-08 11:39:35 +00:00
' cost_id ' : fields . many2one ( ' stock.landed.cost ' , ' Landed Cost ' , required = True , ondelete = ' cascade ' ) ,
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True ) ,
2014-04-24 10:37:52 +00:00
' price_unit ' : fields . float ( ' Unit Price ' , required = True , digits_compute = dp . get_precision ( ' Product Price ' ) ) ,
2014-04-10 05:30:41 +00:00
' split_method ' : fields . selection ( product . SPLIT_METHOD , string = ' Split Method ' , required = True ) ,
2014-04-24 10:37:52 +00:00
' account_id ' : fields . many2one ( ' account.account ' , ' Account ' , domain = [ ( ' type ' , ' <> ' , ' view ' ) , ( ' type ' , ' <> ' , ' closed ' ) ] ) ,
2014-04-08 11:39:35 +00:00
}
2014-04-09 08:52:58 +00:00
class stock_valuation_adjustment_lines ( osv . osv ) :
_name = ' stock.valuation.adjustment.lines '
_description = ' Stock Valuation Adjustment Lines '
2014-04-09 09:39:18 +00:00
def _amount_final ( self , cr , uid , ids , name , args , context = None ) :
result = { }
for line in self . browse ( cr , uid , ids , context = context ) :
2014-04-10 05:49:01 +00:00
result [ line . id ] = {
' former_cost_per_unit ' : 0.0 ,
' final_cost ' : 0.0 ,
}
2014-04-21 09:47:48 +00:00
result [ line . id ] [ ' former_cost_per_unit ' ] = ( line . former_cost / line . quantity if line . quantity else 1.0 )
2014-04-10 05:49:01 +00:00
result [ line . id ] [ ' final_cost ' ] = ( line . former_cost + line . additional_landed_cost )
2014-04-09 09:39:18 +00:00
return result
2014-04-24 10:37:52 +00:00
def _get_name ( self , cr , uid , ids , name , arg , context = None ) :
res = { }
for line in self . browse ( cr , uid , ids , context = context ) :
res [ line . id ] = line . product_id . code or line . product_id . name or ' '
if line . cost_line_id :
res [ line . id ] + = ' - ' + line . cost_line_id . name
return res
2014-04-09 08:52:58 +00:00
_columns = {
2014-04-24 10:37:52 +00:00
' name ' : fields . function ( _get_name , type = ' char ' , string = ' Description ' , store = True ) ,
2014-04-09 08:52:58 +00:00
' cost_id ' : fields . many2one ( ' stock.landed.cost ' , ' Landed Cost ' , required = True , ondelete = ' cascade ' ) ,
2014-04-11 13:10:28 +00:00
' cost_line_id ' : fields . many2one ( ' stock.landed.cost.lines ' , ' Cost Line ' , readonly = True ) ,
' move_id ' : fields . many2one ( ' stock.move ' , ' Stock Move ' , readonly = True ) ,
2014-04-09 08:52:58 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True ) ,
2014-04-24 10:37:52 +00:00
' quantity ' : fields . float ( ' Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) , required = True ) ,
' weight ' : fields . float ( ' Weight ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ) ,
' volume ' : fields . float ( ' Volume ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ) ,
' former_cost ' : fields . float ( ' Former Cost ' , digits_compute = dp . get_precision ( ' Product Price ' ) ) ,
' former_cost_per_unit ' : fields . function ( _amount_final , multi = ' cost ' , string = ' Former Cost(Per Unit) ' , type = ' float ' , digits_compute = dp . get_precision ( ' Account ' ) , store = True ) ,
' additional_landed_cost ' : fields . float ( ' Additional Landed Cost ' , digits_compute = dp . get_precision ( ' Product Price ' ) ) ,
' final_cost ' : fields . function ( _amount_final , multi = ' cost ' , string = ' Final Cost ' , type = ' float ' , digits_compute = dp . get_precision ( ' Account ' ) , store = True ) ,
2014-04-09 09:39:18 +00:00
}
_defaults = {
' quantity ' : 1.0 ,
2014-04-11 09:39:57 +00:00
' weight ' : 1.0 ,
' volume ' : 1.0 ,
2014-04-09 08:52:58 +00:00
}
2014-04-08 11:39:35 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: