2018-01-16 06:58:15 +01:00
# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
2018-01-16 06:58:15 +01:00
import calendar
from datetime import date , datetime
from dateutil . relativedelta import relativedelta
2018-01-16 11:34:37 +01:00
from flectra import api , fields , models , _
from flectra . exceptions import UserError , ValidationError
from flectra . tools import DEFAULT_SERVER_DATE_FORMAT as DF
from flectra . tools import float_compare , float_is_zero
2018-01-16 06:58:15 +01:00
class AccountAssetCategory ( models . Model ) :
_name = ' account.asset.category '
_description = ' Asset category '
active = fields . Boolean ( default = True )
2018-08-02 11:19:29 +02:00
name = fields . Char ( required = True , index = True , string = " Asset Category " )
2018-01-16 06:58:15 +01:00
account_analytic_id = fields . Many2one ( ' account.analytic.account ' , string = ' Analytic Account ' )
account_asset_id = fields . Many2one ( ' account.account ' , string = ' Asset Account ' , required = True , domain = [ ( ' internal_type ' , ' = ' , ' other ' ) , ( ' deprecated ' , ' = ' , False ) ] , help = " Account used to record the purchase of the asset at its original price. " )
account_depreciation_id = fields . Many2one ( ' account.account ' , string = ' Depreciation Entries: Asset Account ' , required = True , domain = [ ( ' internal_type ' , ' = ' , ' other ' ) , ( ' deprecated ' , ' = ' , False ) ] , help = " Account used in the depreciation entries, to decrease the asset value. " )
account_depreciation_expense_id = fields . Many2one ( ' account.account ' , string = ' Depreciation Entries: Expense Account ' , required = True , domain = [ ( ' internal_type ' , ' = ' , ' other ' ) , ( ' deprecated ' , ' = ' , False ) ] , oldname = ' account_income_recognition_id ' , help = " Account used in the periodical entries, to record a part of the asset as expense. " )
journal_id = fields . Many2one ( ' account.journal ' , string = ' Journal ' , required = True )
company_id = fields . Many2one ( ' res.company ' , string = ' Company ' , required = True , default = lambda self : self . env [ ' res.company ' ] . _company_default_get ( ' account.asset.category ' ) )
method = fields . Selection ( [ ( ' linear ' , ' Linear ' ) , ( ' degressive ' , ' Degressive ' ) ] , string = ' Computation Method ' , required = True , default = ' linear ' ,
help = " Choose the method to use to compute the amount of depreciation lines. \n "
" * Linear: Calculated on basis of: Gross Value / Number of Depreciations \n "
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor " )
method_number = fields . Integer ( string = ' Number of Depreciations ' , default = 5 , help = " The number of depreciations needed to depreciate your asset " )
method_period = fields . Integer ( string = ' Period Length ' , default = 1 , help = " State here the time between 2 depreciations, in months " , required = True )
method_progress_factor = fields . Float ( ' Degressive Factor ' , default = 0.3 )
2018-07-24 13:15:11 +02:00
method_time = fields . Selection ( [ ( ' number ' , ' Number of Depreciations ' ) , ( ' end ' , ' Ending Date ' ) ] , string = ' Time Method ' , required = True , default = ' number ' ,
2018-01-16 06:58:15 +01:00
help = " Choose the method to use to compute the dates and number of entries. \n "
" * Number of Entries: Fix the number of entries and the time between 2 depreciations. \n "
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won ' t go beyond. " )
method_end = fields . Date ( ' Ending date ' )
2018-07-24 13:15:11 +02:00
prorata = fields . Selection ( [ ( ' first_january ' , ' First of January ' ) ,
( ' purchase_date ' , ' From the Purchase Date ' ) ,
( ' fiscal_year ' , ' Fiscal Year ' ) ] ,
help = ' Indicates that the first depreciation entry for this asset have to be done from the purchase date or first of January or fiscal year ' )
2018-01-16 06:58:15 +01:00
open_asset = fields . Boolean ( string = ' Auto-confirm Assets ' , help = " Check this if you want to automatically confirm the assets of this category when created by invoices. " )
group_entries = fields . Boolean ( string = ' Group Journal Entries ' , help = " Check this if you want to group the generated entries by categories. " )
type = fields . Selection ( [ ( ' sale ' , ' Sale: Revenue Recognition ' ) , ( ' purchase ' , ' Purchase: Asset ' ) ] , required = True , index = True , default = ' purchase ' )
2018-07-24 13:15:11 +02:00
asset_ids = fields . One2many ( ' account.asset.asset ' , ' category_id ' ,
string = ' Asset Line ' )
2018-01-16 06:58:15 +01:00
@api.onchange ( ' account_asset_id ' )
def onchange_account_asset ( self ) :
if self . type == " purchase " :
self . account_depreciation_id = self . account_asset_id
elif self . type == " sale " :
self . account_depreciation_expense_id = self . account_asset_id
@api.onchange ( ' type ' )
def onchange_type ( self ) :
if self . type == ' sale ' :
2018-07-24 13:15:11 +02:00
self . prorata = ' purchase_date '
2018-01-16 06:58:15 +01:00
self . method_period = 1
else :
self . method_period = 12
@api.onchange ( ' method_time ' )
def _onchange_method_time ( self ) :
if self . method_time != ' number ' :
2018-07-24 13:15:11 +02:00
self . prorata = ' first_january '
@api.onchange ( ' prorata ' )
def onchange_prorata ( self ) :
if self . prorata == ' fiscal_year ' :
self . method_period = 12
2018-01-16 06:58:15 +01:00
class AccountAssetAsset ( models . Model ) :
_name = ' account.asset.asset '
_description = ' Asset/Revenue Recognition '
_inherit = [ ' mail.thread ' ]
entry_count = fields . Integer ( compute = ' _entry_count ' , string = ' # Asset Entries ' )
name = fields . Char ( string = ' Asset Name ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
code = fields . Char ( string = ' Reference ' , size = 32 , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
value = fields . Float ( string = ' Gross Value ' , required = True , readonly = True , digits = 0 , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , oldname = ' purchase_value ' )
currency_id = fields . Many2one ( ' res.currency ' , string = ' Currency ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
default = lambda self : self . env . user . company_id . currency_id . id )
company_id = fields . Many2one ( ' res.company ' , string = ' Company ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
default = lambda self : self . env [ ' res.company ' ] . _company_default_get ( ' account.asset.asset ' ) )
note = fields . Text ( )
category_id = fields . Many2one ( ' account.asset.category ' , string = ' Category ' , required = True , change_default = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
date = fields . Date ( string = ' Date ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , default = fields . Date . context_today , oldname = " purchase_date " )
state = fields . Selection ( [ ( ' draft ' , ' Draft ' ) , ( ' open ' , ' Running ' ) , ( ' close ' , ' Close ' ) ] , ' Status ' , required = True , copy = False , default = ' draft ' ,
help = " When an asset is created, the status is ' Draft ' . \n "
" If the asset is confirmed, the status goes in ' Running ' and the depreciation lines can be posted in the accounting. \n "
" You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status. " )
active = fields . Boolean ( default = True )
partner_id = fields . Many2one ( ' res.partner ' , string = ' Partner ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
2018-07-24 13:15:11 +02:00
product_id = fields . Many2one ( ' product.product ' , string = ' Product ' ,
track_visibility = ' onchange ' )
invoice_count = fields . Integer ( string = " Invoice " ,
compute = " count_invoice " )
remaining_asset_value = fields . Float ( string = " Remaining Asset Value " ,
compute = " get_remaining_value " )
2018-01-16 06:58:15 +01:00
method = fields . Selection ( [ ( ' linear ' , ' Linear ' ) , ( ' degressive ' , ' Degressive ' ) ] , string = ' Computation Method ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , default = ' linear ' ,
help = " Choose the method to use to compute the amount of depreciation lines. \n * Linear: Calculated on basis of: Gross Value / Number of Depreciations \n "
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor " )
method_number = fields . Integer ( string = ' Number of Depreciations ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , default = 5 , help = " The number of depreciations needed to depreciate your asset " )
method_period = fields . Integer ( string = ' Number of Months in a Period ' , required = True , readonly = True , default = 12 , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = " The amount of time between two depreciations, in months " )
method_end = fields . Date ( string = ' Ending Date ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
method_progress_factor = fields . Float ( string = ' Degressive Factor ' , readonly = True , default = 0.3 , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
value_residual = fields . Float ( compute = ' _amount_residual ' , method = True , digits = 0 , string = ' Residual Value ' )
2018-07-24 13:15:11 +02:00
method_time = fields . Selection ( [ ( ' number ' , ' Number of Entries ' ) , ( ' end ' , ' Ending Date ' ) ] , string = ' Time Method ' , required = True , readonly = True , default = ' number ' , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , track_visibility = ' onchange ' ,
help = " Choose the method to use to compute the dates and number of depreciation lines. \n "
" * Number of Depreciation lines: Fix the number of depreciation lines and the time between 2 depreciations. \n "
2018-01-16 06:58:15 +01:00
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won ' t go beyond. " )
2018-07-24 13:15:11 +02:00
sale_date = fields . Date ( string = ' Sale Date ' )
prorata = fields . Selection ( [ ( ' first_january ' , ' First of January ' ) ,
( ' purchase_date ' , ' From the Purchase Date ' ) ,
( ' fiscal_year ' , ' Fiscal Year ' ) ] ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = ' Indicates that the first depreciation entry for this asset have to be done from the purchase date or first of January or fiscal year ' )
2018-01-16 06:58:15 +01:00
depreciation_line_ids = fields . One2many ( ' account.asset.depreciation.line ' , ' asset_id ' , string = ' Depreciation Lines ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] , ' open ' : [ ( ' readonly ' , False ) ] } )
salvage_value = fields . Float ( string = ' Salvage Value ' , digits = 0 , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = " It is the amount you plan to have that you cannot depreciate. " )
invoice_id = fields . Many2one ( ' account.invoice ' , string = ' Invoice ' , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , copy = False )
type = fields . Selection ( related = " category_id.type " , string = ' Type ' , required = True )
2018-07-25 11:55:19 +02:00
customer_invoice_count = fields . Integer ( string = " Invoice " ,
compute = " count_invoice " )
2018-01-16 06:58:15 +01:00
2018-07-24 13:15:11 +02:00
@api.onchange ( ' prorata ' )
def onchange_prorata ( self ) :
if self . prorata == ' fiscal_year ' :
self . method_period = 12
@api.multi
def get_remaining_value ( self ) :
for record in self :
posted_line_id = record . depreciation_line_ids . search (
[ ( ' move_check ' , ' = ' , True ) , ( ' asset_id ' , ' = ' , record . id ) ] ,
limit = 1 , order = ' id desc ' )
record . remaining_asset_value = posted_line_id . remaining_value
@api.onchange ( ' product_id ' )
def on_product_id_change ( self ) :
self . category_id = self . product_id . asset_category_id
self . value = self . product_id . list_price
@api.depends ( ' prorata ' )
def get_fiscal_date ( self , date ) :
if self . prorata == ' fiscal_year ' :
last_day = self . company_id . fiscalyear_last_day
last_month = self . company_id . fiscalyear_last_month
if date :
date = datetime . strptime ( date , DF ) . date ( )
else :
date = datetime . now ( )
2018-07-28 09:26:48 +02:00
year = date . year
fiscal_date = date . replace ( month = last_month , day = last_day , year = year )
if fiscal_date < date :
fiscal_date = date . replace ( month = last_month , day = last_day , year = year + 1 )
return datetime . strftime ( fiscal_date , ' % Y- % m- %d ' )
2018-07-24 13:15:11 +02:00
@api.multi
def count_invoice ( self ) :
for self_obj in self :
2018-07-25 11:55:19 +02:00
count = self_obj . invoice_id . search_count (
[ ( ' asset_id ' , ' = ' , self_obj . id ) , ( ' type ' , ' = ' , ' in_invoice ' ) ] )
2018-07-24 13:15:11 +02:00
self_obj . invoice_count = count
2018-07-25 11:55:19 +02:00
customer_invoice_count = self_obj . invoice_id . search_count (
[ ( ' asset_id ' , ' = ' , self_obj . id ) , ( ' type ' , ' = ' , ' out_invoice ' ) ] )
self_obj . customer_invoice_count = customer_invoice_count
2018-07-24 13:15:11 +02:00
def redirect_to_invoice ( self ) :
2018-07-25 11:55:19 +02:00
if self . _context . get ( ' customer ' ) :
action = self . env . ref ( ' account.action_invoice_tree1 ' ) . read ( ) [ 0 ]
action [ ' domain ' ] = [ ( ' asset_id ' , ' = ' , self . id ) , ( ' type ' , ' = ' , ' out_invoice ' ) ]
else :
action = self . env . ref ( ' account.action_invoice_tree2 ' ) . read ( ) [ 0 ]
action [ ' domain ' ] = [ ( ' asset_id ' , ' = ' , self . id ) , ( ' type ' , ' = ' , ' in_invoice ' ) ]
return action
2018-07-24 13:15:11 +02:00
2018-01-16 06:58:15 +01:00
@api.multi
def unlink ( self ) :
for asset in self :
if asset . state in [ ' open ' , ' close ' ] :
raise UserError ( _ ( ' You cannot delete a document is in %s state. ' ) % ( asset . state , ) )
for depreciation_line in asset . depreciation_line_ids :
if depreciation_line . move_id :
raise UserError ( _ ( ' You cannot delete a document that contains posted entries. ' ) )
return super ( AccountAssetAsset , self ) . unlink ( )
@api.multi
def _get_last_depreciation_date ( self ) :
"""
@param id : ids of a account . asset . asset objects
@return : Returns a dictionary of the effective dates of the last depreciation entry made for given asset ids . If there isn ' t any, return the purchase date of this asset
"""
self . env . cr . execute ( """
SELECT a . id as id , COALESCE ( MAX ( m . date ) , a . date ) AS date
FROM account_asset_asset a
LEFT JOIN account_asset_depreciation_line rel ON ( rel . asset_id = a . id )
LEFT JOIN account_move m ON ( rel . move_id = m . id )
WHERE a . id IN % s
GROUP BY a . id , m . date """ , (tuple(self.ids),))
result = dict ( self . env . cr . fetchall ( ) )
return result
@api.model
def _cron_generate_entries ( self ) :
self . compute_generated_entries ( datetime . today ( ) )
@api.model
def compute_generated_entries ( self , date , asset_type = None ) :
# Entries generated : one by grouped category and one by asset from ungrouped category
created_move_ids = [ ]
type_domain = [ ]
if asset_type :
type_domain = [ ( ' type ' , ' = ' , asset_type ) ]
ungrouped_assets = self . env [ ' account.asset.asset ' ] . search ( type_domain + [ ( ' state ' , ' = ' , ' open ' ) , ( ' category_id.group_entries ' , ' = ' , False ) ] )
created_move_ids + = ungrouped_assets . _compute_entries ( date , group_entries = False )
for grouped_category in self . env [ ' account.asset.category ' ] . search ( type_domain + [ ( ' group_entries ' , ' = ' , True ) ] ) :
assets = self . env [ ' account.asset.asset ' ] . search ( [ ( ' state ' , ' = ' , ' open ' ) , ( ' category_id ' , ' = ' , grouped_category . id ) ] )
created_move_ids + = assets . _compute_entries ( date , group_entries = True )
return created_move_ids
def _compute_board_amount ( self , sequence , residual_amount , amount_to_depr , undone_dotation_number , posted_depreciation_line_ids , total_days , depreciation_date ) :
amount = 0
if sequence == undone_dotation_number :
amount = residual_amount
else :
if self . method == ' linear ' :
2018-07-24 13:15:11 +02:00
# Manage last line with remaining amount
if self . prorata == ' fiscal_year ' :
undone_dotation_number = undone_dotation_number - 1
2018-01-16 06:58:15 +01:00
amount = amount_to_depr / ( undone_dotation_number - len ( posted_depreciation_line_ids ) )
2018-07-24 13:15:11 +02:00
if self . prorata == ' purchase_date ' :
2018-01-16 06:58:15 +01:00
amount = amount_to_depr / self . method_number
if sequence == 1 :
if self . method_period % 12 != 0 :
date = datetime . strptime ( self . date , ' % Y- % m- %d ' )
month_days = calendar . monthrange ( date . year , date . month ) [ 1 ]
days = month_days - date . day + 1
amount = ( amount_to_depr / self . method_number ) / month_days * days
else :
days = ( self . company_id . compute_fiscalyear_dates ( depreciation_date ) [ ' date_to ' ] - depreciation_date ) . days + 1
amount = ( amount_to_depr / self . method_number ) / total_days * days
2018-07-24 13:15:11 +02:00
elif self . prorata == ' fiscal_year ' and sequence == 1 :
fiscal_date = datetime . strptime ( self . date or fields . Datetime . today ( ) , ' % Y- % m- %d ' )
days = ( self . company_id . compute_fiscalyear_dates ( depreciation_date ) [ ' date_to ' ] - fiscal_date . date ( ) ) . days
amount = ( amount_to_depr / self . method_number ) / total_days * days
2018-01-16 06:58:15 +01:00
elif self . method == ' degressive ' :
amount = residual_amount * self . method_progress_factor
2018-07-24 13:15:11 +02:00
if self . prorata == ' purchase_date ' :
2018-01-16 06:58:15 +01:00
if sequence == 1 :
if self . method_period % 12 != 0 :
date = datetime . strptime ( self . date , ' % Y- % m- %d ' )
month_days = calendar . monthrange ( date . year , date . month ) [ 1 ]
days = month_days - date . day + 1
amount = ( residual_amount * self . method_progress_factor ) / month_days * days
else :
days = ( self . company_id . compute_fiscalyear_dates ( depreciation_date ) [ ' date_to ' ] - depreciation_date ) . days + 1
amount = ( residual_amount * self . method_progress_factor ) / total_days * days
2018-07-24 13:15:11 +02:00
elif self . prorata == ' fiscal_year ' and sequence == 1 :
fiscal_date = datetime . strptime ( self . date or fields . Datetime . today ( ) , ' % Y- % m- %d ' )
days = ( self . company_id . compute_fiscalyear_dates ( depreciation_date ) [ ' date_to ' ] - fiscal_date . date ( ) ) . days
amount = ( residual_amount * self . method_progress_factor ) / total_days * days
2018-01-16 06:58:15 +01:00
return amount
def _compute_board_undone_dotation_nb ( self , depreciation_date , total_days ) :
undone_dotation_number = self . method_number
if self . method_time == ' end ' :
end_date = datetime . strptime ( self . method_end , DF ) . date ( )
undone_dotation_number = 0
while depreciation_date < = end_date :
depreciation_date = date ( depreciation_date . year , depreciation_date . month , depreciation_date . day ) + relativedelta ( months = + self . method_period )
undone_dotation_number + = 1
2018-07-24 13:15:11 +02:00
if self . prorata in [ ' purchase_date ' , ' fiscal_year ' ] :
2018-01-16 06:58:15 +01:00
undone_dotation_number + = 1
return undone_dotation_number
@api.multi
def compute_depreciation_board ( self ) :
self . ensure_one ( )
posted_depreciation_line_ids = self . depreciation_line_ids . filtered ( lambda x : x . move_check ) . sorted ( key = lambda l : l . depreciation_date )
unposted_depreciation_line_ids = self . depreciation_line_ids . filtered ( lambda x : not x . move_check )
# Remove old unposted depreciation lines. We cannot use unlink() with One2many field
commands = [ ( 2 , line_id . id , False ) for line_id in unposted_depreciation_line_ids ]
if self . value_residual != 0.0 :
amount_to_depr = residual_amount = self . value_residual
2018-07-24 13:15:11 +02:00
if self . prorata == ' purchase_date ' :
2018-01-16 06:58:15 +01:00
# if we already have some previous validated entries, starting date is last entry + method perio
if posted_depreciation_line_ids and posted_depreciation_line_ids [ - 1 ] . depreciation_date :
last_depreciation_date = datetime . strptime ( posted_depreciation_line_ids [ - 1 ] . depreciation_date , DF ) . date ( )
depreciation_date = last_depreciation_date + relativedelta ( months = + self . method_period )
else :
depreciation_date = datetime . strptime ( self . _get_last_depreciation_date ( ) [ self . id ] , DF ) . date ( )
2018-07-24 13:15:11 +02:00
elif self . prorata == ' fiscal_year ' :
if posted_depreciation_line_ids and posted_depreciation_line_ids [ - 1 ] . depreciation_date :
last_depreciation_date = datetime . strptime ( posted_depreciation_line_ids [ - 1 ] . depreciation_date , DF ) . date ( )
depreciation_date = last_depreciation_date + relativedelta ( months = + self . method_period )
else :
fiscal_date = self . get_fiscal_date ( self . date )
depreciation_date = datetime . strptime ( fiscal_date , DF ) . date ( )
2018-01-16 06:58:15 +01:00
else :
# depreciation_date = 1st of January of purchase year if annual valuation, 1st of
# purchase month in other cases
if self . method_period > = 12 :
asset_date = datetime . strptime ( self . date [ : 4 ] + ' -01-01 ' , DF ) . date ( )
else :
asset_date = datetime . strptime ( self . date [ : 7 ] + ' -01 ' , DF ) . date ( )
# if we already have some previous validated entries, starting date isn't 1st January but last entry + method period
if posted_depreciation_line_ids and posted_depreciation_line_ids [ - 1 ] . depreciation_date :
last_depreciation_date = datetime . strptime ( posted_depreciation_line_ids [ - 1 ] . depreciation_date , DF ) . date ( )
depreciation_date = last_depreciation_date + relativedelta ( months = + self . method_period )
else :
depreciation_date = asset_date
day = depreciation_date . day
month = depreciation_date . month
year = depreciation_date . year
total_days = ( year % 4 ) and 365 or 366
undone_dotation_number = self . _compute_board_undone_dotation_nb ( depreciation_date , total_days )
for x in range ( len ( posted_depreciation_line_ids ) , undone_dotation_number ) :
sequence = x + 1
amount = self . _compute_board_amount ( sequence , residual_amount , amount_to_depr , undone_dotation_number , posted_depreciation_line_ids , total_days , depreciation_date )
amount = self . currency_id . round ( amount )
if float_is_zero ( amount , precision_rounding = self . currency_id . rounding ) :
continue
residual_amount - = amount
vals = {
' amount ' : amount ,
' asset_id ' : self . id ,
' sequence ' : sequence ,
' name ' : ( self . code or ' ' ) + ' / ' + str ( sequence ) ,
' remaining_value ' : residual_amount ,
' depreciated_value ' : self . value - ( self . salvage_value + residual_amount ) ,
' depreciation_date ' : depreciation_date . strftime ( DF ) ,
}
commands . append ( ( 0 , False , vals ) )
# Considering Depr. Period as months
depreciation_date = date ( year , month , day ) + relativedelta ( months = + self . method_period )
day = depreciation_date . day
month = depreciation_date . month
year = depreciation_date . year
self . write ( { ' depreciation_line_ids ' : commands } )
return True
@api.multi
def validate ( self ) :
self . write ( { ' state ' : ' open ' } )
fields = [
' method ' ,
' method_number ' ,
' method_period ' ,
' method_end ' ,
' method_progress_factor ' ,
' method_time ' ,
' salvage_value ' ,
' invoice_id ' ,
]
ref_tracked_fields = self . env [ ' account.asset.asset ' ] . fields_get ( fields )
for asset in self :
tracked_fields = ref_tracked_fields . copy ( )
if asset . method == ' linear ' :
del ( tracked_fields [ ' method_progress_factor ' ] )
if asset . method_time != ' end ' :
del ( tracked_fields [ ' method_end ' ] )
else :
del ( tracked_fields [ ' method_number ' ] )
dummy , tracking_value_ids = asset . _message_track ( tracked_fields , dict . fromkeys ( fields ) )
asset . message_post ( subject = _ ( ' Asset created ' ) , tracking_value_ids = tracking_value_ids )
def _get_disposal_moves ( self ) :
move_ids = [ ]
for asset in self :
unposted_depreciation_line_ids = asset . depreciation_line_ids . filtered ( lambda x : not x . move_check )
if unposted_depreciation_line_ids :
old_values = {
' method_end ' : asset . method_end ,
' method_number ' : asset . method_number ,
}
# Remove all unposted depr. lines
commands = [ ( 2 , line_id . id , False ) for line_id in unposted_depreciation_line_ids ]
# Create a new depr. line with the residual amount and post it
sequence = len ( asset . depreciation_line_ids ) - len ( unposted_depreciation_line_ids ) + 1
today = datetime . today ( ) . strftime ( DF )
vals = {
' amount ' : asset . value_residual ,
' asset_id ' : asset . id ,
' sequence ' : sequence ,
' name ' : ( asset . code or ' ' ) + ' / ' + str ( sequence ) ,
' remaining_value ' : 0 ,
' depreciated_value ' : asset . value - asset . salvage_value , # the asset is completely depreciated
' depreciation_date ' : today ,
}
commands . append ( ( 0 , False , vals ) )
asset . write ( { ' depreciation_line_ids ' : commands , ' method_end ' : today , ' method_number ' : sequence } )
tracked_fields = self . env [ ' account.asset.asset ' ] . fields_get ( [ ' method_number ' , ' method_end ' ] )
changes , tracking_value_ids = asset . _message_track ( tracked_fields , old_values )
if changes :
asset . message_post ( subject = _ ( ' Asset sold or disposed. Accounting entry awaiting for validation. ' ) , tracking_value_ids = tracking_value_ids )
move_ids + = asset . depreciation_line_ids [ - 1 ] . create_move ( post_move = False )
return move_ids
@api.multi
def set_to_close ( self ) :
move_ids = self . _get_disposal_moves ( )
if move_ids :
name = _ ( ' Disposal Move ' )
view_mode = ' form '
if len ( move_ids ) > 1 :
name = _ ( ' Disposal Moves ' )
view_mode = ' tree,form '
return {
' name ' : name ,
' view_type ' : ' form ' ,
' view_mode ' : view_mode ,
' res_model ' : ' account.move ' ,
' type ' : ' ir.actions.act_window ' ,
' target ' : ' current ' ,
' res_id ' : move_ids [ 0 ] ,
}
@api.multi
def set_to_draft ( self ) :
self . write ( { ' state ' : ' draft ' } )
@api.one
@api.depends ( ' value ' , ' salvage_value ' , ' depreciation_line_ids.move_check ' , ' depreciation_line_ids.amount ' )
def _amount_residual ( self ) :
total_amount = 0.0
for line in self . depreciation_line_ids :
if line . move_check :
total_amount + = line . amount
self . value_residual = self . value - total_amount - self . salvage_value
@api.onchange ( ' company_id ' )
def onchange_company_id ( self ) :
self . currency_id = self . company_id . currency_id . id
@api.multi
@api.depends ( ' depreciation_line_ids.move_id ' )
def _entry_count ( self ) :
for asset in self :
res = self . env [ ' account.asset.depreciation.line ' ] . search_count ( [ ( ' asset_id ' , ' = ' , asset . id ) , ( ' move_id ' , ' != ' , False ) ] )
asset . entry_count = res or 0
@api.one
@api.constrains ( ' prorata ' , ' method_time ' )
def _check_prorata ( self ) :
2018-07-24 13:15:11 +02:00
if self . prorata == ' purchase_date ' and self . method_time != ' number ' :
2018-01-16 06:58:15 +01:00
raise ValidationError ( _ ( ' Prorata temporis can be applied only for time method " number of depreciations " . ' ) )
@api.onchange ( ' category_id ' )
def onchange_category_id ( self ) :
vals = self . onchange_category_id_values ( self . category_id . id )
# We cannot use 'write' on an object that doesn't exist yet
if vals :
for k , v in vals [ ' value ' ] . items ( ) :
setattr ( self , k , v )
def onchange_category_id_values ( self , category_id ) :
if category_id :
category = self . env [ ' account.asset.category ' ] . browse ( category_id )
return {
' value ' : {
' method ' : category . method ,
' method_number ' : category . method_number ,
' method_time ' : category . method_time ,
' method_period ' : category . method_period ,
' method_progress_factor ' : category . method_progress_factor ,
' method_end ' : category . method_end ,
' prorata ' : category . prorata ,
}
}
@api.onchange ( ' method_time ' )
def onchange_method_time ( self ) :
if self . method_time != ' number ' :
2018-07-24 13:15:11 +02:00
self . prorata = ' first_january '
2018-01-16 06:58:15 +01:00
@api.multi
def copy_data ( self , default = None ) :
if default is None :
default = { }
default [ ' name ' ] = self . name + _ ( ' (copy) ' )
return super ( AccountAssetAsset , self ) . copy_data ( default )
@api.multi
def _compute_entries ( self , date , group_entries = False ) :
depreciation_ids = self . env [ ' account.asset.depreciation.line ' ] . search ( [
( ' asset_id ' , ' in ' , self . ids ) , ( ' depreciation_date ' , ' <= ' , date ) ,
( ' move_check ' , ' = ' , False ) ] )
if group_entries :
return depreciation_ids . create_grouped_move ( )
return depreciation_ids . create_move ( )
@api.model
def create ( self , vals ) :
asset = super ( AccountAssetAsset , self . with_context ( mail_create_nolog = True ) ) . create ( vals )
2018-07-24 13:15:11 +02:00
asset . sudo ( ) . compute_depreciation_board ( )
2018-01-16 06:58:15 +01:00
return asset
@api.multi
def write ( self , vals ) :
res = super ( AccountAssetAsset , self ) . write ( vals )
if ' depreciation_line_ids ' not in vals and ' state ' not in vals :
for rec in self :
rec . compute_depreciation_board ( )
return res
@api.multi
def open_entries ( self ) :
move_ids = [ ]
for asset in self :
for depreciation_line in asset . depreciation_line_ids :
if depreciation_line . move_id :
move_ids . append ( depreciation_line . move_id . id )
return {
' name ' : _ ( ' Journal Entries ' ) ,
' view_type ' : ' form ' ,
' view_mode ' : ' tree,form ' ,
' res_model ' : ' account.move ' ,
' view_id ' : False ,
' type ' : ' ir.actions.act_window ' ,
' domain ' : [ ( ' id ' , ' in ' , move_ids ) ] ,
}
class AccountAssetDepreciationLine ( models . Model ) :
_name = ' account.asset.depreciation.line '
_description = ' Asset depreciation line '
name = fields . Char ( string = ' Depreciation Name ' , required = True , index = True )
sequence = fields . Integer ( required = True )
asset_id = fields . Many2one ( ' account.asset.asset ' , string = ' Asset ' , required = True , ondelete = ' cascade ' )
parent_state = fields . Selection ( related = ' asset_id.state ' , string = ' State of Asset ' )
amount = fields . Float ( string = ' Current Depreciation ' , digits = 0 , required = True )
remaining_value = fields . Float ( string = ' Next Period Depreciation ' , digits = 0 , required = True )
depreciated_value = fields . Float ( string = ' Cumulative Depreciation ' , required = True )
depreciation_date = fields . Date ( ' Depreciation Date ' , index = True )
move_id = fields . Many2one ( ' account.move ' , string = ' Depreciation Entry ' )
move_check = fields . Boolean ( compute = ' _get_move_check ' , string = ' Linked ' , track_visibility = ' always ' , store = True )
move_posted_check = fields . Boolean ( compute = ' _get_move_posted_check ' , string = ' Posted ' , track_visibility = ' always ' , store = True )
2018-07-24 13:15:11 +02:00
product_id = fields . Many2one ( related = ' asset_id.product_id ' , relation = " product.product " , string = " SID " )
begin_value = fields . Float ( compute = ' _get_begin_value ' , string = " Beginning Value " , store = True )
@api.multi
@api.depends ( ' asset_id.value ' , ' asset_id.salvage_value ' )
def _get_begin_value ( self ) :
for line in self :
line . begin_value = line . asset_id . value - line . asset_id . salvage_value
2018-01-16 06:58:15 +01:00
@api.multi
@api.depends ( ' move_id ' )
def _get_move_check ( self ) :
for line in self :
line . move_check = bool ( line . move_id )
@api.multi
@api.depends ( ' move_id.state ' )
def _get_move_posted_check ( self ) :
for line in self :
line . move_posted_check = True if line . move_id and line . move_id . state == ' posted ' else False
2018-07-24 13:15:11 +02:00
@api.multi
def action_move_cancel ( self ) :
for line in self . asset_id . depreciation_line_ids :
if line . id > = self . id and line . move_check :
line . move_check = False
line . move_id . button_cancel ( )
2018-01-16 06:58:15 +01:00
@api.multi
def create_move ( self , post_move = True ) :
created_moves = self . env [ ' account.move ' ]
prec = self . env [ ' decimal.precision ' ] . precision_get ( ' Account ' )
for line in self :
if line . move_id :
2018-07-24 13:15:11 +02:00
# raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.'))
line . move_id . post ( )
line . write ( { ' move_check ' : True } )
return [ x . id for x in line . move_id ]
2018-01-16 06:58:15 +01:00
category_id = line . asset_id . category_id
depreciation_date = self . env . context . get ( ' depreciation_date ' ) or line . depreciation_date or fields . Date . context_today ( self )
company_currency = line . asset_id . company_id . currency_id
current_currency = line . asset_id . currency_id
amount = current_currency . with_context ( date = depreciation_date ) . compute ( line . amount , company_currency )
asset_name = line . asset_id . name + ' ( %s / %s ) ' % ( line . sequence , len ( line . asset_id . depreciation_line_ids ) )
move_line_1 = {
' name ' : asset_name ,
' account_id ' : category_id . account_depreciation_id . id ,
' debit ' : 0.0 if float_compare ( amount , 0.0 , precision_digits = prec ) > 0 else - amount ,
' credit ' : amount if float_compare ( amount , 0.0 , precision_digits = prec ) > 0 else 0.0 ,
' journal_id ' : category_id . journal_id . id ,
' partner_id ' : line . asset_id . partner_id . id ,
' analytic_account_id ' : category_id . account_analytic_id . id if category_id . type == ' sale ' else False ,
' currency_id ' : company_currency != current_currency and current_currency . id or False ,
' amount_currency ' : company_currency != current_currency and - 1.0 * line . amount or 0.0 ,
}
move_line_2 = {
' name ' : asset_name ,
' account_id ' : category_id . account_depreciation_expense_id . id ,
' credit ' : 0.0 if float_compare ( amount , 0.0 , precision_digits = prec ) > 0 else - amount ,
' debit ' : amount if float_compare ( amount , 0.0 , precision_digits = prec ) > 0 else 0.0 ,
' journal_id ' : category_id . journal_id . id ,
' partner_id ' : line . asset_id . partner_id . id ,
' analytic_account_id ' : category_id . account_analytic_id . id if category_id . type == ' purchase ' else False ,
' currency_id ' : company_currency != current_currency and current_currency . id or False ,
' amount_currency ' : company_currency != current_currency and line . amount or 0.0 ,
}
move_vals = {
' ref ' : line . asset_id . code ,
' date ' : depreciation_date or False ,
' journal_id ' : category_id . journal_id . id ,
2018-07-24 13:15:11 +02:00
' asset_id ' : line . asset_id . id ,
2018-01-16 06:58:15 +01:00
' line_ids ' : [ ( 0 , 0 , move_line_1 ) , ( 0 , 0 , move_line_2 ) ] ,
}
move = self . env [ ' account.move ' ] . create ( move_vals )
line . write ( { ' move_id ' : move . id , ' move_check ' : True } )
created_moves | = move
if post_move and created_moves :
created_moves . filtered ( lambda m : any ( m . asset_depreciation_ids . mapped ( ' asset_id.category_id.open_asset ' ) ) ) . post ( )
return [ x . id for x in created_moves ]
@api.multi
def create_grouped_move ( self , post_move = True ) :
if not self . exists ( ) :
return [ ]
created_moves = self . env [ ' account.move ' ]
category_id = self [ 0 ] . asset_id . category_id # we can suppose that all lines have the same category
depreciation_date = self . env . context . get ( ' depreciation_date ' ) or fields . Date . context_today ( self )
amount = 0.0
for line in self :
# Sum amount of all depreciation lines
company_currency = line . asset_id . company_id . currency_id
current_currency = line . asset_id . currency_id
amount + = current_currency . compute ( line . amount , company_currency )
name = category_id . name + _ ( ' (grouped) ' )
move_line_1 = {
' name ' : name ,
' account_id ' : category_id . account_depreciation_id . id ,
' debit ' : 0.0 ,
' credit ' : amount ,
' journal_id ' : category_id . journal_id . id ,
' analytic_account_id ' : category_id . account_analytic_id . id if category_id . type == ' sale ' else False ,
}
move_line_2 = {
' name ' : name ,
' account_id ' : category_id . account_depreciation_expense_id . id ,
' credit ' : 0.0 ,
' debit ' : amount ,
' journal_id ' : category_id . journal_id . id ,
' analytic_account_id ' : category_id . account_analytic_id . id if category_id . type == ' purchase ' else False ,
}
move_vals = {
' ref ' : category_id . name ,
' date ' : depreciation_date or False ,
' journal_id ' : category_id . journal_id . id ,
' line_ids ' : [ ( 0 , 0 , move_line_1 ) , ( 0 , 0 , move_line_2 ) ] ,
}
move = self . env [ ' account.move ' ] . create ( move_vals )
self . write ( { ' move_id ' : move . id , ' move_check ' : True } )
created_moves | = move
if post_move and created_moves :
self . post_lines_and_close_asset ( )
created_moves . post ( )
return [ x . id for x in created_moves ]
@api.multi
def post_lines_and_close_asset ( self ) :
# we re-evaluate the assets to determine whether we can close them
for line in self :
line . log_message_when_posted ( )
asset = line . asset_id
if asset . currency_id . is_zero ( asset . value_residual ) :
asset . message_post ( body = _ ( " Document closed. " ) )
asset . write ( { ' state ' : ' close ' } )
@api.multi
def log_message_when_posted ( self ) :
def _format_message ( message_description , tracked_values ) :
message = ' '
if message_description :
message = ' <span> %s </span> ' % message_description
for name , values in tracked_values . items ( ) :
message + = ' <div> • <b> %s </b>: ' % name
message + = ' %s </div> ' % values
return message
for line in self :
if line . move_id and line . move_id . state == ' draft ' :
partner_name = line . asset_id . partner_id . name
currency_name = line . asset_id . currency_id . name
msg_values = { _ ( ' Currency ' ) : currency_name , _ ( ' Amount ' ) : line . amount }
if partner_name :
msg_values [ _ ( ' Partner ' ) ] = partner_name
msg = _format_message ( _ ( ' Depreciation line posted. ' ) , msg_values )
line . asset_id . message_post ( body = msg )
@api.multi
def unlink ( self ) :
for record in self :
if record . move_check :
if record . asset_id . category_id . type == ' purchase ' :
msg = _ ( " You cannot delete posted depreciation lines. " )
else :
msg = _ ( " You cannot delete posted installment lines. " )
raise UserError ( msg )
return super ( AccountAssetDepreciationLine , self ) . unlink ( )