# -*- encoding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution # # Copyright (C) 2010-2012 OpenERP s.a. (). # Copyright (c) 2014 Noviat nv/sa (www.noviat.com). All rights reserved. # # 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 . # ############################################################################## import time import calendar from datetime import datetime from dateutil.relativedelta import relativedelta from openerp.osv import fields, orm from openerp.addons.decimal_precision import decimal_precision as dp from openerp import tools from openerp.tools.translate import _ from openerp import SUPERUSER_ID import logging _logger = logging.getLogger(__name__) class dummy_fy(object): def __init__(self, *args, **argv): for key, arg in argv.items(): setattr(self, key, arg) class account_asset_category(orm.Model): _name = 'account.asset.category' _description = 'Asset category' _order = 'name' def _get_method(self, cr, uid, context=None): return[ ('linear', _('Linear')), ('degressive', _('Degressive')), ('degr-linear', _('Degressive-Linear')) ] def _get_method_time(self, cr, uid, context=None): return [ ('year', _('Number of Years')), # ('number', _('Number of Depreciations')), # ('end', _('Ending Date')) ] def _get_company(self, cr, uid, context=None): return self.pool.get('res.company')._company_default_get( cr, uid, 'account.asset.category', context=context) _columns = { 'name': fields.char('Name', size=64, required=True, select=1), 'note': fields.text('Note'), 'account_analytic_id': fields.many2one( 'account.analytic.account', 'Analytic account'), 'account_asset_id': fields.many2one( 'account.account', 'Asset Account', required=True, domain=[('type', '=', 'other')]), 'account_depreciation_id': fields.many2one( 'account.account', 'Depreciation Account', required=True, domain=[('type', '=', 'other')]), 'account_expense_depreciation_id': fields.many2one( 'account.account', 'Depr. Expense Account', required=True, domain=[('type', '=', 'other')]), 'account_plus_value_id': fields.many2one( 'account.account', 'Plus-Value Account', domain=[('type', '=', 'other')]), 'account_min_value_id': fields.many2one( 'account.account', 'Min-Value Account', domain=[('type', '=', 'other')]), 'account_residual_value_id': fields.many2one( 'account.account', 'Residual Value Account', domain=[('type', '=', 'other')]), 'journal_id': fields.many2one( 'account.journal', 'Journal', required=True), 'company_id': fields.many2one( 'res.company', 'Company', required=True), 'parent_id': fields.many2one( 'account.asset.asset', 'Parent Asset', domain=[('type', '=', 'view')]), 'method': fields.selection( _get_method, 'Computation Method', required=True, 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" " * Degressive-Linear (only for Time Method = Year): " "Degressive becomes linear when the annual linear " "depreciation exceeds the annual degressive depreciation"), 'method_number': fields.integer( 'Number of Years', help="The number of years needed to depreciate your asset"), 'method_period': fields.selection([ ('month', 'Month'), ('quarter', 'Quarter'), ('year', 'Year'), ], 'Period Length', required=True, help="Period length for the depreciation accounting entries"), 'method_progress_factor': fields.float('Degressive Factor'), 'method_time': fields.selection( _get_method_time, 'Time Method', required=True, help="Choose the method to use to compute the dates and " "number of depreciation lines.\n" " * Number of Years: Specify the number of years " "for the depreciation.\n" # " * Number of Depreciations: Fix the number of " # "depreciation lines and the time between 2 depreciations.\n" # " * Ending Date: Choose the time between 2 depreciations " # "and the date the depreciations won't go beyond." ), 'prorata': fields.boolean( 'Prorata Temporis', help="Indicates that the first depreciation entry for this asset " "has to be done from the depreciation start date instead of " "the first day of the fiscal year."), 'open_asset': fields.boolean( 'Skip Draft State', help="Check this if you want to automatically confirm the assets " "of this category when created by invoices."), 'active': fields.boolean('Active'), } _defaults = { 'active': 1, 'company_id': _get_company, 'method': 'linear', 'method_number': 5, 'method_time': 'year', 'method_period': 'year', 'method_progress_factor': 0.3, } def _check_method(self, cr, uid, ids, context=None): for asset in self.browse(cr, uid, ids, context=context): if asset.method == 'degr-linear' and asset.method_time != 'year': return False return True _constraints = [( _check_method, "Degressive-Linear is only supported for Time Method = Year.", ['method'] )] def onchange_method_time(self, cr, uid, ids, method_time='number', context=None): res = {'value': {}} if method_time != 'year': res['value'] = {'prorata': True} return res def create(self, cr, uid, vals, context=None): if vals.get('method_time') != 'year' and not vals.get('prorata'): vals['prorata'] = True categ_id = super(account_asset_category, self).create( cr, uid, vals, context=context) acc_obj = self.pool.get('account.account') acc_id = vals.get('account_asset_id') if acc_id: account = acc_obj.browse(cr, uid, acc_id) if not account.asset_category_id: acc_obj.write( cr, uid, [acc_id], {'asset_category_id': categ_id}) return categ_id def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] if vals.get('method_time'): if vals['method_time'] != 'year' and not vals.get('prorata'): vals['prorata'] = True super(account_asset_category, self).write(cr, uid, ids, vals, context) acc_obj = self.pool.get('account.account') for categ in self.browse(cr, uid, ids, context): acc_id = vals.get('account_asset_id') if acc_id: account = acc_obj.browse(cr, uid, acc_id) if not account.asset_category_id: acc_obj.write( cr, uid, [acc_id], {'asset_category_id': categ.id}) return True class account_asset_recompute_trigger(orm.Model): _name = 'account.asset.recompute.trigger' _description = "Asset table recompute triggers" _columns = { 'reason': fields.char( 'Reason', size=64, required=True), 'company_id': fields.many2one( 'res.company', 'Company', required=True), 'date_trigger': fields.datetime( 'Trigger Date', readonly=True, help="Date of the event triggering the need to " "recompute the Asset Tables."), 'date_completed': fields.datetime( 'Completion Date', readonly=True), 'state': fields.selection( [('open', 'Open'), ('done', 'Done')], 'State', readonly=True), } _defaults = { 'state': 'open', } class account_asset_asset(orm.Model): _name = 'account.asset.asset' _description = 'Asset' _order = 'date_start desc, name' _parent_store = True def unlink(self, cr, uid, ids, context=None): for asset in self.browse(cr, uid, ids, context=context): if asset.state != 'draft': raise orm.except_orm( _('Invalid action!'), _("You can only delete assets in draft state.")) if asset.account_move_line_ids: raise orm.except_orm( _('Error!'), _("You cannot delete an asset that contains " "posted depreciation lines.")) parent = asset.parent_id super(account_asset_asset, self).unlink( cr, uid, [asset.id], context=context) if parent: # Trigger store function parent.write({'salvage_value': parent.salvage_value}) return True def _get_period(self, cr, uid, context=None): ctx = dict(context or {}, account_period_prefer_normal=True) periods = self.pool.get('account.period').find(cr, uid, context=ctx) if periods: return periods[0] else: return False def _get_fy_duration(self, cr, uid, fy_id, option='days', context=None): """ Returns fiscal year duration. @param option: - days: duration in days - months: duration in months, a started month is counted as a full month - years: duration in calendar years, considering also leap years """ cr.execute( "SELECT date_start, date_stop, " "date_stop-date_start+1 AS total_days " "FROM account_fiscalyear WHERE id=%s" % fy_id) fy_vals = cr.dictfetchall()[0] days = fy_vals['total_days'] months = (int(fy_vals['date_stop'][:4]) - int(fy_vals['date_start'][:4])) * 12 + \ (int(fy_vals['date_stop'][5:7]) - int(fy_vals['date_start'][5:7])) + 1 if option == 'days': return days elif option == 'months': return months elif option == 'years': fy_date_start = datetime.strptime( fy_vals['date_start'], '%Y-%m-%d') fy_year_start = int(fy_vals['date_start'][:4]) fy_date_stop = datetime.strptime( fy_vals['date_stop'], '%Y-%m-%d') fy_year_stop = int(fy_vals['date_stop'][:4]) year = fy_year_start cnt = fy_year_stop - fy_year_start + 1 for i in range(cnt): cy_days = calendar.isleap(year) and 366 or 365 if i == 0: # first year if fy_date_stop.year == year: duration = (fy_date_stop - fy_date_start).days + 1 else: duration = ( datetime(year, 12, 31) - fy_date_start).days + 1 factor = float(duration) / cy_days elif i == cnt - 1: # last year duration = ( fy_date_stop - datetime(year, 01, 01)).days + 1 factor += float(duration) / cy_days else: factor += 1.0 year += 1 return factor def _get_fy_duration_factor(self, cr, uid, entry, asset, firstyear, context=None): """ localization: override this method to change the logic used to calculate the impact of extended/shortened fiscal years """ duration_factor = 1.0 fy_id = entry['fy_id'] if asset.prorata: if firstyear: depreciation_date_start = datetime.strptime( asset.date_start, '%Y-%m-%d') fy_date_stop = entry['date_stop'] first_fy_asset_days = \ (fy_date_stop - depreciation_date_start).days + 1 if fy_id: first_fy_duration = self._get_fy_duration( cr, uid, fy_id, option='days') first_fy_year_factor = self._get_fy_duration( cr, uid, fy_id, option='years') duration_factor = \ float(first_fy_asset_days) / first_fy_duration \ * first_fy_year_factor else: first_fy_duration = \ calendar.isleap(entry['date_start'].year) \ and 366 or 365 duration_factor = \ float(first_fy_asset_days) / first_fy_duration elif fy_id: duration_factor = self._get_fy_duration( cr, uid, fy_id, option='years') elif fy_id: fy_months = self._get_fy_duration( cr, uid, fy_id, option='months') duration_factor = float(fy_months) / 12 return duration_factor def _get_depreciation_start_date(self, cr, uid, asset, fy, context=None): """ In case of 'Linear': the first month is counted as a full month if the fiscal year starts in the middle of a month. """ if asset.prorata: depreciation_start_date = datetime.strptime( asset.date_start, '%Y-%m-%d') else: fy_date_start = datetime.strptime(fy.date_start, '%Y-%m-%d') depreciation_start_date = datetime( fy_date_start.year, fy_date_start.month, 1) return depreciation_start_date def _get_depreciation_stop_date(self, cr, uid, asset, depreciation_start_date, context=None): if asset.method_time == 'year': depreciation_stop_date = depreciation_start_date + \ relativedelta(years=asset.method_number, days=-1) elif asset.method_time == 'number': if asset.method_period == 'month': depreciation_stop_date = depreciation_start_date + \ relativedelta(months=asset.method_number, days=-1) elif asset.method_period == 'quarter': depreciation_stop_date = depreciation_start_date + \ relativedelta(months=asset.method_number * 3, days=-1) elif asset.method_period == 'year': depreciation_stop_date = depreciation_start_date + \ relativedelta(years=asset.method_number, days=-1) elif asset.method_time == 'end': depreciation_stop_date = datetime.strptime( asset.method_end, '%Y-%m-%d') return depreciation_stop_date def _compute_year_amount(self, cr, uid, asset, amount_to_depr, residual_amount, context=None): """ Localization: override this method to change the degressive-linear calculation logic according to local legislation. """ if asset.method_time == 'year': divisor = asset.method_number elif asset.method_time == 'number': if asset.method_period == 'month': divisor = asset.method_number / 12.0 elif asset.method_period == 'quarter': divisor = asset.method_number * 3 / 12.0 elif asset.method_period == 'year': divisor = asset.method_number elif asset.method_time == 'end': duration = \ (datetime.strptime(asset.method_end, '%Y-%m-%d') - datetime.strptime(asset.date_start, '%Y-%m-%d')).days + 1 divisor = duration / 365.0 year_amount_linear = amount_to_depr / divisor if asset.method == 'linear': return year_amount_linear year_amount_degressive = residual_amount * \ asset.method_progress_factor if asset.method == 'degressive': return year_amount_degressive if asset.method == 'degr-linear': if year_amount_linear > year_amount_degressive: return year_amount_linear else: return year_amount_degressive else: raise orm.except_orm( _('Programming Error!'), _("Illegal value %s in asset.method.") % asset.method) def _compute_depreciation_table(self, cr, uid, asset, context=None): if not context: context = {} table = [] if not asset.method_number: return table context['company_id'] = asset.company_id.id fy_obj = self.pool.get('account.fiscalyear') init_flag = False try: fy_id = fy_obj.find(cr, uid, asset.date_start, context=context) fy = fy_obj.browse(cr, uid, fy_id) if fy.state == 'done': init_flag = True fy_date_start = datetime.strptime(fy.date_start, '%Y-%m-%d') fy_date_stop = datetime.strptime(fy.date_stop, '%Y-%m-%d') except: # The following logic is used when no fiscalyear # is defined for the asset start date: # - We lookup the first fiscal year defined in the system # - The 'undefined' fiscal years are assumed to be years # with a duration equals to calendar year cr.execute( "SELECT id, date_start, date_stop " "FROM account_fiscalyear ORDER BY date_stop ASC LIMIT 1") first_fy = cr.dictfetchone() first_fy_date_start = datetime.strptime( first_fy['date_start'], '%Y-%m-%d') asset_date_start = datetime.strptime(asset.date_start, '%Y-%m-%d') fy_date_start = first_fy_date_start if asset_date_start > fy_date_start: asset_ref = asset.code and '%s (ref: %s)' \ % (asset.name, asset.code) or asset.name raise orm.except_orm( _('Error!'), _("You cannot compute a depreciation table for an asset " "starting in an undefined future fiscal year." "\nPlease correct the start date for asset '%s'.") % asset_ref) while asset_date_start < fy_date_start: fy_date_start = fy_date_start - relativedelta(years=1) fy_date_stop = fy_date_start + relativedelta(years=1, days=-1) fy_id = False fy = dummy_fy( date_start=fy_date_start.strftime('%Y-%m-%d'), date_stop=fy_date_stop.strftime('%Y-%m-%d'), id=False, state='done', dummy=True) init_flag = True depreciation_start_date = self._get_depreciation_start_date( cr, uid, asset, fy, context=context) depreciation_stop_date = self._get_depreciation_stop_date( cr, uid, asset, depreciation_start_date, context=context) while fy_date_start <= depreciation_stop_date: table.append({ 'fy_id': fy_id, 'date_start': fy_date_start, 'date_stop': fy_date_stop, 'init': init_flag}) fy_date_start = fy_date_stop + relativedelta(days=1) try: fy_id = fy_obj.find(cr, uid, fy_date_start, context=context) init_flag = False except: fy_id = False if fy_id: fy = fy_obj.browse(cr, uid, fy_id) if fy.state == 'done': init_flag = True fy_date_stop = datetime.strptime(fy.date_stop, '%Y-%m-%d') else: fy_date_stop = fy_date_stop + relativedelta(years=1) digits = self.pool.get('decimal.precision').precision_get( cr, uid, 'Account') amount_to_depr = residual_amount = asset.asset_value # step 1: calculate depreciation amount per fiscal year fy_residual_amount = residual_amount i_max = len(table) - 1 asset_sign = asset.asset_value >= 0 and 1 or -1 for i, entry in enumerate(table): year_amount = self._compute_year_amount( cr, uid, asset, amount_to_depr, fy_residual_amount, context=context) if asset.method_period == 'year': period_amount = year_amount elif asset.method_period == 'quarter': period_amount = year_amount/4 elif asset.method_period == 'month': period_amount = year_amount/12 if i == i_max: fy_amount = fy_residual_amount else: firstyear = i == 0 and True or False fy_factor = self._get_fy_duration_factor( cr, uid, entry, asset, firstyear, context=context) fy_amount = year_amount * fy_factor if asset_sign * (fy_amount - fy_residual_amount) > 0: fy_amount = fy_residual_amount period_amount = round(period_amount, digits) fy_amount = round(fy_amount, digits) entry.update({ 'period_amount': period_amount, 'fy_amount': fy_amount, }) fy_residual_amount -= fy_amount if round(fy_residual_amount, digits) == 0: break i_max = i table = table[:i_max + 1] # step 2: spread depreciation amount per fiscal year # over the depreciation periods fy_residual_amount = residual_amount line_date = False for i, entry in enumerate(table): period_amount = entry['period_amount'] fy_amount = entry['fy_amount'] period_duration = (asset.method_period == 'year' and 12) \ or (asset.method_period == 'quarter' and 3) or 1 if period_duration == 12: if asset_sign * (fy_amount - fy_residual_amount) > 0: fy_amount = fy_residual_amount lines = [{'date': entry['date_stop'], 'amount': fy_amount}] fy_residual_amount -= fy_amount elif period_duration in [1, 3]: lines = [] fy_amount_check = 0.0 if not line_date: if period_duration == 3: m = [x for x in [3, 6, 9, 12] if x >= depreciation_start_date.month][0] line_date = depreciation_start_date + \ relativedelta(month=m, day=31) else: line_date = depreciation_start_date + \ relativedelta(months=0, day=31) while line_date <= \ min(entry['date_stop'], depreciation_stop_date) and \ asset_sign * (fy_residual_amount - period_amount) > 0: lines.append({'date': line_date, 'amount': period_amount}) fy_residual_amount -= period_amount fy_amount_check += period_amount line_date = line_date + \ relativedelta(months=period_duration, day=31) if i == i_max and \ (not lines or depreciation_stop_date > lines[-1]['date']): # last year, last entry period_amount = fy_residual_amount lines.append({'date': line_date, 'amount': period_amount}) fy_amount_check += period_amount if round(fy_amount_check - fy_amount, digits) != 0: # handle rounding and extended/shortened # fiscal year deviations diff = fy_amount_check - fy_amount fy_residual_amount += diff if i == 0: # first year: deviation in first period lines[0]['amount'] = period_amount - diff else: # other years: deviation in last period lines[-1]['amount'] = period_amount - diff else: raise orm.except_orm( _('Programming Error!'), _("Illegal value %s in asset.method_period.") % asset.method_period) for line in lines: line['depreciated_value'] = amount_to_depr - residual_amount residual_amount -= line['amount'] line['remaining_value'] = residual_amount entry['lines'] = lines return table def _get_depreciation_entry_name(self, cr, uid, asset, seq, context=None): """ use this method to customise the name of the accounting entry """ return (asset.code or str(asset.id)) + '/' + str(seq) def compute_depreciation_board(self, cr, uid, ids, context=None): if not context: context = {} depreciation_lin_obj = self.pool.get( 'account.asset.depreciation.line') digits = self.pool.get('decimal.precision').precision_get( cr, uid, 'Account') for asset in self.browse(cr, uid, ids, context=context): if asset.value_residual == 0.0: continue domain = [ ('asset_id', '=', asset.id), ('type', '=', 'depreciate'), '|', ('move_check', '=', True), ('init_entry', '=', True)] posted_depreciation_line_ids = depreciation_lin_obj.search( cr, uid, domain, order='line_date desc') if (len(posted_depreciation_line_ids) > 0): last_depreciation_line = depreciation_lin_obj.browse( cr, uid, posted_depreciation_line_ids[0], context=context) else: last_depreciation_line = False domain = [ ('asset_id', '=', asset.id), ('type', '=', 'depreciate'), ('move_id', '=', False), ('init_entry', '=', False)] old_depreciation_line_ids = depreciation_lin_obj.search( cr, uid, domain) if old_depreciation_line_ids: depreciation_lin_obj.unlink( cr, uid, old_depreciation_line_ids, context=context) context['company_id'] = asset.company_id.id table = self._compute_depreciation_table( cr, uid, asset, context=context) if not table: continue # group lines prior to depreciation start period depreciation_start_date = datetime.strptime( asset.date_start, '%Y-%m-%d') lines = table[0]['lines'] lines1 = [] lines2 = [] flag = lines[0]['date'] < depreciation_start_date for line in lines: if flag: lines1.append(line) if line['date'] >= depreciation_start_date: flag = False else: lines2.append(line) if lines1: def group_lines(x, y): y.update({'amount': x['amount'] + y['amount']}) return y lines1 = [reduce(group_lines, lines1)] lines1[0]['depreciated_value'] = 0.0 table[0]['lines'] = lines1 + lines2 # check table with posted entries and # recompute in case of deviation if (len(posted_depreciation_line_ids) > 0): last_depreciation_date = datetime.strptime( last_depreciation_line.line_date, '%Y-%m-%d') last_date_in_table = table[-1]['lines'][-1]['date'] if last_date_in_table <= last_depreciation_date: raise orm.except_orm( _('Error!'), _("The duration of the asset conflicts with the " "posted depreciation table entry dates.")) for table_i, entry in enumerate(table): residual_amount_table = \ entry['lines'][-1]['remaining_value'] if entry['date_start'] <= last_depreciation_date \ <= entry['date_stop']: break if entry['date_stop'] == last_depreciation_date: table_i += 1 line_i = 0 else: entry = table[table_i] date_min = entry['date_start'] for line_i, line in enumerate(entry['lines']): residual_amount_table = line['remaining_value'] if date_min <= last_depreciation_date <= line['date']: break date_min = line['date'] if line['date'] == last_depreciation_date: line_i += 1 table_i_start = table_i line_i_start = line_i # check if residual value corresponds with table # and adjust table when needed cr.execute( "SELECT COALESCE(SUM(amount), 0.0) " "FROM account_asset_depreciation_line " "WHERE id IN %s", (tuple(posted_depreciation_line_ids),)) res = cr.fetchone() depreciated_value = res[0] residual_amount = asset.asset_value - depreciated_value amount_diff = round( residual_amount_table - residual_amount, digits) if amount_diff: entry = table[table_i_start] if entry['fy_id']: cr.execute( "SELECT COALESCE(SUM(amount), 0.0) " "FROM account_asset_depreciation_line " "WHERE id in %s " " AND line_date >= %s and line_date <= %s", (tuple(posted_depreciation_line_ids), entry['date_start'], entry['date_stop'])) res = cr.fetchone() fy_amount_check = res[0] else: fy_amount_check = 0.0 lines = entry['lines'] for line in lines[line_i_start:-1]: line['depreciated_value'] = depreciated_value depreciated_value += line['amount'] fy_amount_check += line['amount'] residual_amount -= line['amount'] line['remaining_value'] = residual_amount lines[-1]['depreciated_value'] = depreciated_value lines[-1]['amount'] = entry['fy_amount'] - fy_amount_check else: table_i_start = 0 line_i_start = 0 seq = len(posted_depreciation_line_ids) depr_line_id = last_depreciation_line and last_depreciation_line.id last_date = table[-1]['lines'][-1]['date'] for entry in table[table_i_start:]: for line in entry['lines'][line_i_start:]: seq += 1 name = self._get_depreciation_entry_name( cr, uid, asset, seq, context=context) if line['date'] == last_date: # ensure that the last entry of the table always # depreciates the remaining value cr.execute( "SELECT COALESCE(SUM(amount), 0.0) " "FROM account_asset_depreciation_line " "WHERE type = 'depreciate' AND line_date < %s " "AND asset_id = %s ", (last_date, asset.id)) res = cr.fetchone() amount = asset.asset_value - res[0] else: amount = line['amount'] vals = { 'previous_id': depr_line_id, 'amount': amount, 'asset_id': asset.id, 'name': name, 'line_date': line['date'].strftime('%Y-%m-%d'), 'init_entry': entry['init'], } depr_line_id = depreciation_lin_obj.create( cr, uid, vals, context=context) line_i_start = 0 return True def validate(self, cr, uid, ids, context=None): if context is None: context = {} currency_obj = self.pool.get('res.currency') for asset in self.browse(cr, uid, ids, context=context): if asset.type == 'normal' and currency_obj.is_zero( cr, uid, asset.company_id.currency_id, asset.value_residual): asset.write({'state': 'close'}, context=context) else: asset.write({'state': 'open'}, context=context) return True def remove(self, cr, uid, ids, context=None): for asset in self.browse(cr, uid, ids, context): ctx = dict(context, active_ids=ids, active_id=ids[0]) if asset.value_residual: ctx.update({'early_removal': True}) return { 'name': _("Generate Asset Removal entries"), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'account.asset.remove', 'target': 'new', 'type': 'ir.actions.act_window', 'context': ctx, 'nodestroy': True, } def set_to_draft(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'draft'}, context=context) def _asset_value_compute(self, cr, uid, asset, context=None): if asset.type == 'view': asset_value = 0.0 else: asset_value = asset.purchase_value - asset.salvage_value return asset_value def _asset_value(self, cr, uid, ids, name, args, context=None): res = {} for asset in self.browse(cr, uid, ids, context): if asset.type == 'normal': res[asset.id] = self._asset_value_compute( cr, uid, asset, context) else: def _value_get(record): asset_value = self._asset_value_compute( cr, uid, asset, context) for rec in record.child_ids: asset_value += \ rec.type == 'normal' and \ self._asset_value_compute(cr, uid, rec, context) \ or _value_get(rec) return asset_value res[asset.id] = _value_get(asset) return res def _compute_depreciation(self, cr, uid, ids, name, args, context=None): res = {} for asset in self.browse(cr, uid, ids, context=context): res[asset.id] = {} child_ids = self.search(cr, uid, [('parent_id', 'child_of', [asset.id]), ('type', '=', 'normal')], context=context) if child_ids: cr.execute( "SELECT COALESCE(SUM(amount),0.0) AS amount " "FROM account_asset_depreciation_line " "WHERE asset_id in %s " "AND type in ('depreciate','remove') " "AND (init_entry=TRUE OR move_check=TRUE)", (tuple(child_ids),)) value_depreciated = cr.fetchone()[0] else: value_depreciated = 0.0 res[asset.id]['value_residual'] = \ asset.asset_value - value_depreciated res[asset.id]['value_depreciated'] = \ value_depreciated return res def _move_line_check(self, cr, uid, ids, name, args, context=None): res = dict.fromkeys(ids, False) for asset in self.browse(cr, uid, ids, context=context): for line in asset.depreciation_line_ids: if line.move_id: res[asset.id] = True continue return res def onchange_purchase_salvage_value( self, cr, uid, ids, purchase_value, salvage_value, date_start, context=None): if not context: context = {} val = {} purchase_value = purchase_value or 0.0 salvage_value = salvage_value or 0.0 if purchase_value or salvage_value: val['asset_value'] = purchase_value - salvage_value if ids: aadl_obj = self.pool.get('account.asset.depreciation.line') dl_create_ids = aadl_obj.search( cr, uid, [('type', '=', 'create'), ('asset_id', 'in', ids)]) aadl_obj.write( cr, uid, dl_create_ids, {'amount': val['asset_value'], 'line_date': date_start}) return {'value': val} def _get_assets(self, cr, uid, ids, context=None): asset_ids = [] for asset in self.browse(cr, uid, ids, context=context): def _parent_get(record): asset_ids.append(record.id) if record.parent_id: _parent_get(record.parent_id) _parent_get(asset) return asset_ids def _get_assets_from_dl(self, cr, uid, ids, context=None): asset_ids = [] for dl in filter( lambda x: x.type in ['depreciate', 'remove'] and (x.init_entry or x.move_id), self.pool.get('account.asset.depreciation.line').browse( cr, uid, ids, context=context)): res = [] def _parent_get(record): res.append(record.id) if record.parent_id: res.append(_parent_get(record.parent_id)) _parent_get(dl.asset_id) for asset_id in res: if asset_id not in asset_ids: asset_ids.append(asset_id) return asset_ids def _get_method(self, cr, uid, context=None): return self.pool.get('account.asset.category')._get_method( cr, uid, context) def _get_method_time(self, cr, uid, context=None): return self.pool.get('account.asset.category')._get_method_time( cr, uid, context) def _get_company(self, cr, uid, context=None): return self.pool.get('res.company')._company_default_get( cr, uid, 'account.asset.asset', context=context) _columns = { 'account_move_line_ids': fields.one2many( 'account.move.line', 'asset_id', 'Entries', readonly=True), 'move_line_check': fields.function( _move_line_check, method=True, type='boolean', string='Has accounting entries'), 'name': fields.char( 'Asset Name', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}), 'code': fields.char( 'Reference', size=32, readonly=True, states={'draft': [('readonly', False)]}), 'purchase_value': fields.float( 'Purchase Value', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="\nThe Asset Value is calculated as follows:" "\nPurchase Value - Salvage Value."), 'asset_value': fields.function( _asset_value, method=True, digits_compute=dp.get_precision('Account'), string='Asset Value', store={ 'account.asset.asset': ( _get_assets, ['purchase_value', 'salvage_value', 'parent_id'], 10), }, help="This amount represent the initial value of the asset."), 'value_residual': fields.function( _compute_depreciation, method=True, multi='cd', digits_compute=dp.get_precision('Account'), string='Residual Value', store={ 'account.asset.asset': ( _get_assets, [ 'purchase_value', 'salvage_value', 'parent_id', 'depreciation_line_ids' ], 20), 'account.asset.depreciation.line': ( _get_assets_from_dl, ['amount', 'init_entry', 'move_id'], 20), }), 'value_depreciated': fields.function( _compute_depreciation, method=True, multi='cd', digits_compute=dp.get_precision('Account'), string='Depreciated Value', store={ 'account.asset.asset': ( _get_assets, [ 'purchase_value', 'salvage_value', 'parent_id', 'depreciation_line_ids' ], 30), 'account.asset.depreciation.line': ( _get_assets_from_dl, ['amount', 'init_entry', 'move_id'], 30), }), 'salvage_value': fields.float( 'Salvage Value', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft': [('readonly', False)]}, help="The estimated value that an asset will realize upon " "its sale at the end of its useful life.\n" "This value is used to determine the depreciation amounts."), 'note': fields.text('Note'), 'category_id': fields.many2one( 'account.asset.category', 'Asset Category', change_default=True, readonly=True, states={'draft': [('readonly', False)]}), 'parent_id': fields.many2one( 'account.asset.asset', 'Parent Asset', readonly=True, states={'draft': [('readonly', False)]}, domain=[('type', '=', 'view')], ondelete='restrict'), 'parent_left': fields.integer('Parent Left', select=1), 'parent_right': fields.integer('Parent Right', select=1), 'child_ids': fields.one2many( 'account.asset.asset', 'parent_id', 'Child Assets'), 'date_start': fields.date( 'Asset Start Date', readonly=True, states={'draft': [('readonly', False)]}, help="You should manually add depreciation lines " "with the depreciations of previous fiscal years " "if the Depreciation Start Date is different from the date " "for which accounting entries need to be generated."), 'date_remove': fields.date('Asset Removal Date', readonly=True), 'state': fields.selection([ ('draft', 'Draft'), ('open', 'Running'), ('close', 'Close'), ('removed', 'Removed'), ], 'Status', required=True, 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 " "to the accounting.\n" "If the last depreciation line is posted, " "the asset goes into the 'Close' status.\n" "When the removal entries are generated, " "the asset goes into the 'Removed' status."), 'active': fields.boolean('Active'), 'partner_id': fields.many2one( 'res.partner', 'Partner', readonly=True, states={'draft': [('readonly', False)]}), 'method': fields.selection( _get_method, 'Computation Method', required=True, readonly=True, states={'draft': [('readonly', False)]}, 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" " * Degressive-Linear (only for Time Method = Year): " "Degressive becomes linear when the annual linear " "depreciation exceeds the annual degressive depreciation"), 'method_number': fields.integer( 'Number of Years', readonly=True, states={'draft': [('readonly', False)]}, help="The number of years needed to depreciate your asset"), 'method_period': fields.selection([ ('month', 'Month'), ('quarter', 'Quarter'), ('year', 'Year'), ], 'Period Length', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Period length for the depreciation accounting entries"), 'method_end': fields.date( 'Ending Date', readonly=True, states={'draft': [('readonly', False)]}), 'method_progress_factor': fields.float( 'Degressive Factor', readonly=True, states={'draft': [('readonly', False)]}), 'method_time': fields.selection( _get_method_time, 'Time Method', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Choose the method to use to compute the dates and " "number of depreciation lines.\n" " * Number of Years: Specify the number of years " "for the depreciation.\n" # " * Number of Depreciations: Fix the number of " # "depreciation lines and the time between 2 depreciations.\n" # " * Ending Date: Choose the time between 2 depreciations " # "and the date the depreciations won't go beyond." ), 'prorata': fields.boolean( 'Prorata Temporis', readonly=True, states={'draft': [('readonly', False)]}, help="Indicates that the first depreciation entry for this asset " "have to be done from the depreciation start date instead " "of the first day of the fiscal year."), 'history_ids': fields.one2many( 'account.asset.history', 'asset_id', 'History', readonly=True), 'depreciation_line_ids': fields.one2many( 'account.asset.depreciation.line', 'asset_id', 'Depreciation Lines', readonly=True, states={'draft': [('readonly', False)]}), 'type': fields.selection([ ('view', 'View'), ('normal', 'Normal'), ], 'Type', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'company_id': fields.many2one( 'res.company', 'Company', required=True, readonly=True), 'company_currency_id': fields.related( 'company_id', 'currency_id', string='Company Currency', type='many2one', relation='res.currency', store=True, readonly=True,), } _defaults = { 'date_start': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'), 'active': True, 'state': 'draft', 'method': 'linear', 'method_number': 5, 'method_time': 'year', 'method_period': 'year', 'method_progress_factor': 0.3, 'type': 'normal', 'company_id': _get_company, } def _check_recursion(self, cr, uid, ids, context=None, parent=None): return super(account_asset_asset, self)._check_recursion( cr, uid, ids, context=context, parent=parent) def _check_method(self, cr, uid, ids, context=None): for asset in self.browse(cr, uid, ids, context=context): if asset.method == 'degr-linear' and asset.method_time != 'year': return False return True _constraints = [ (_check_recursion, "Error ! You can not create recursive assets.", ['parent_id']), (_check_method, "Degressive-Linear is only supported for Time Method = Year.", ['method']), ] def onchange_type(self, cr, uid, ids, asset_type, context=None): res = {'value': {}} if asset_type == 'view': res['value'] = { 'date_start': False, 'category_id': False, 'purchase_value': 0.0, 'salvage_value': 0.0, 'code': False, } for asset in self.browse(cr, uid, ids, context): if asset.depreciation_line_ids: self.pool.get('account.asset.depreciation.line').unlink( cr, uid, [x.id for x in asset.depreciation_line_ids], context) return res def onchange_category_id(self, cr, uid, ids, category_id, context=None): for asset in self.browse(cr, uid, ids, context): for line in asset.depreciation_line_ids: if line.move_id: raise orm.except_orm( _('Error!'), _("You cannot change the category of an asset " "with accounting entries.")) res = {'value': {}} asset_categ_obj = self.pool.get('account.asset.category') if category_id: category_obj = asset_categ_obj.browse( cr, uid, category_id, context=context) res['value'] = { 'parent_id': category_obj.parent_id.id, 'method': category_obj.method, 'method_number': category_obj.method_number, 'method_time': category_obj.method_time, 'method_period': category_obj.method_period, 'method_progress_factor': category_obj.method_progress_factor, 'prorata': category_obj.prorata, } return res def onchange_method_time(self, cr, uid, ids, method_time='number', context=None): res = {'value': {}} if method_time != 'year': res['value'] = {'prorata': True} return res def copy(self, cr, uid, id, default=None, context=None): if default is None: default = {} if context is None: context = {} default.update({ 'depreciation_line_ids': [], 'account_move_line_ids': [], 'state': 'draft', 'history_ids': []}) return super(account_asset_asset, self).copy( cr, uid, id, default, context=context) def _compute_entries(self, cr, uid, ids, period_id, check_triggers=False, context=None): # To DO : add ir_cron job calling this method to # generate periodical accounting entries if context is None: context = {} result = [] period_obj = self.pool.get('account.period') depreciation_obj = self.pool.get('account.asset.depreciation.line') period = period_obj.browse(cr, uid, period_id, context=context) if check_triggers: recompute_obj = self.pool.get('account.asset.recompute.trigger') recompute_ids = recompute_obj.search( cr, SUPERUSER_ID, [('state', '=', 'open')]) if recompute_ids: recompute_triggers = recompute_obj.read( cr, uid, recompute_ids, ['company_id']) assets = self.browse(cr, uid, ids, context=context) for asset in assets: depreciation_ids = depreciation_obj.search(cr, uid, [ ('asset_id', '=', asset.id), ('type', '=', 'depreciate'), ('init_entry', '=', False), ('line_date', '<', period.date_start), ('move_check', '=', False)], context=context) if depreciation_ids: for line in depreciation_obj.browse( cr, uid, depreciation_ids): asset_ref = asset.code and '%s (ref: %s)' \ % (asset.name, asset.code) or asset.name raise orm.except_orm( _('Error!'), _("Asset '%s' contains unposted lines " "prior to the selected period." "\nPlease post these entries first !") % asset_ref) if check_triggers and recompute_ids: triggers = filter( lambda x: x['company_id'][0] == asset.company_id.id, recompute_triggers) if triggers: self.compute_depreciation_board( cr, uid, [asset.id], context=context) depreciation_ids = depreciation_obj.search(cr, uid, [ ('asset_id', 'in', ids), ('type', '=', 'depreciate'), ('init_entry', '=', False), ('line_date', '<=', period.date_stop), ('line_date', '>=', period.date_start), ('move_check', '=', False)], context=context) for depreciation in depreciation_obj.browse( cr, uid, depreciation_ids, context=context): context.update({'depreciation_date': depreciation.line_date}) result += depreciation_obj.create_move( cr, uid, [depreciation.id], context=context) if check_triggers and recompute_ids: asset_company_ids = set([x.company_id.id for x in assets]) triggers = filter( lambda x: x['company_id'][0] in asset_company_ids, recompute_triggers) if triggers: recompute_vals = { 'date_completed': time.strftime( tools.DEFAULT_SERVER_DATETIME_FORMAT), 'state': 'done', } trigger_ids = [x['id'] for x in triggers] recompute_obj.write( cr, SUPERUSER_ID, trigger_ids, recompute_vals) return result def create(self, cr, uid, vals, context=None): if not context: context = {} if vals.get('method_time') != 'year' and not vals.get('prorata'): vals['prorata'] = True asset_id = super(account_asset_asset, self).create( cr, uid, vals, context=context) if context.get('create_asset_from_move_line'): # Trigger compute of asset_value self.write(cr, uid, [asset_id], {'salvage_value': 0.0}) asset = self.browse(cr, uid, asset_id, context) if asset.type == 'normal': # create first asset line asset_line_obj = self.pool.get('account.asset.depreciation.line') line_name = self._get_depreciation_entry_name( cr, uid, asset, 0, context=context) asset_line_vals = { 'amount': asset.asset_value, 'asset_id': asset_id, 'name': line_name, 'line_date': asset.date_start, 'init_entry': True, 'type': 'create', } asset_line_id = asset_line_obj.create( cr, uid, asset_line_vals, context=context) if context.get('create_asset_from_move_line'): asset_line_obj.write( cr, uid, [asset_line_id], {'move_id': context['move_id']}) return asset_id def write(self, cr, uid, ids, vals, context=None): if not context: context = {} if vals.get('method_time'): if vals['method_time'] != 'year' and not vals.get('prorata'): vals['prorata'] = True for asset in self.browse(cr, uid, ids, context): asset_type = vals.get('type') or asset.type super(account_asset_asset, self).write( cr, uid, [asset.id], vals, context) if asset_type == 'view' or \ context.get('asset_validate_from_write'): continue if asset.category_id.open_asset and \ context.get('create_asset_from_move_line'): self.compute_depreciation_board( cr, uid, [asset.id], context=context) # extra context to avoid recursion self.validate( cr, uid, [asset.id], context=dict(context, asset_validate_from_write=True)) return True def open_entries(self, cr, uid, ids, context=None): if context is None: context = {} cr.execute("SELECT move_id, date FROM account_move_line " "WHERE asset_id IN %s ORDER BY date ASC", (tuple(ids),)) res = cr.fetchall() am_ids = [x[0] for x in res] return { 'name': _("Journal Entries"), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move', 'view_id': False, 'type': 'ir.actions.act_window', 'context': context, 'nodestroy': True, 'domain': [('id', 'in', am_ids)], } def open_move_lines(self, cr, uid, ids, context=None): cr.execute( "SELECT aml2.id FROM account_move_line aml " "INNER JOIN account_move am ON am.id=aml.move_id " "INNER JOIN account_move_line aml2 ON aml2.move_id = am.id " "WHERE aml.asset_id IN %s", (tuple(ids),)) res = cr.fetchall() aml_ids = [x[0] for x in res] return { 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move.line', 'view_id': False, 'type': 'ir.actions.act_window', 'context': context, 'nodestroy': True, 'domain': [('id', 'in', aml_ids)], } class account_asset_depreciation_line(orm.Model): _name = 'account.asset.depreciation.line' _description = 'Asset depreciation line' def _compute(self, cr, uid, ids, name, args, context=None): res = {} dlines = self.browse(cr, uid, ids) if not dlines: return res asset_value = dlines[0].asset_id.asset_value dlines = filter(lambda x: x.type == 'depreciate', dlines) dlines = sorted(dlines, key=lambda dl: dl.line_date) for i, dl in enumerate(dlines): if i == 0: depreciated_value = dl.previous_id and \ (asset_value - dl.previous_id.remaining_value) or 0.0 remaining_value = asset_value - depreciated_value - dl.amount else: depreciated_value += dl.previous_id.amount remaining_value -= dl.amount res[dl.id] = { 'depreciated_value': depreciated_value, 'remaining_value': remaining_value, } return res def _move_check(self, cr, uid, ids, name, args, context=None): res = {} for line in self.browse(cr, uid, ids, context=context): res[line.id] = bool(line.move_id) return res def _get_dl(self, cr, uid, ids, context=None): assets = [] for dl in filter( lambda x: x.type == 'depreciate', self.browse(cr, uid, ids, context=context)): assets.append(dl.asset_id) assets = set(assets) result = [] for asset in assets: result += [x.id for x in asset.depreciation_line_ids] return result _order = 'type, line_date' _columns = { 'name': fields.char('Depreciation Name', size=64, readonly=True), 'asset_id': fields.many2one( 'account.asset.asset', 'Asset', required=True, ondelete='cascade'), 'previous_id': fields.many2one( 'account.asset.depreciation.line', 'Previous Depreciation Line', readonly=True), 'parent_state': fields.related( 'asset_id', 'state', type='char', string='State of Asset'), 'asset_value': fields.related( 'asset_id', 'asset_value', type='float', string='Asset Value'), 'amount': fields.float( 'Amount', digits_compute=dp.get_precision('Account'), required=True), 'remaining_value': fields.function( _compute, method=True, digits_compute=dp.get_precision('Account'), string='Next Period Depreciation', store={ 'account.asset.depreciation.line': (_get_dl, ['amount'], 10), }, multi='all'), 'depreciated_value': fields.function( _compute, method=True, digits_compute=dp.get_precision('Account'), string='Amount Already Depreciated', store={ 'account.asset.depreciation.line': (_get_dl, ['amount'], 10), }, multi='all'), 'line_date': fields.date('Date', required=True), 'move_id': fields.many2one( 'account.move', 'Depreciation Entry', readonly=True), 'move_check': fields.function( _move_check, method=True, type='boolean', string='Posted', store={ 'account.asset.depreciation.line': ( lambda self, cr, uid, ids, c={}: ids, ['move_id'], 10), }), 'type': fields.selection([ ('create', 'Asset Value'), ('depreciate', 'Depreciation'), ('remove', 'Asset Removal'), ], 'Type', readonly=True), 'init_entry': fields.boolean( 'Initial Balance Entry', help="Set this flag for entries of previous fiscal years " "for which OpenERP has not generated accounting entries."), } _defaults = { 'type': 'depreciate', } def onchange_amount(self, cr, uid, ids, dl_type, asset_value, amount, depreciated_value, context=None): res = {} if dl_type == 'depreciate': res['value'] = { 'remaining_value': asset_value - depreciated_value - amount} return res def unlink(self, cr, uid, ids, context=None): for dl in self.browse(cr, uid, ids, context): if dl.type == 'create': raise orm.except_orm( _('Error!'), _("You cannot remove an asset line " "of type 'Asset Value'.")) elif dl.move_id: raise orm.except_orm( _('Error!'), _("You cannot delete a depreciation line with " "an associated accounting entry.")) previous_id = dl.previous_id and dl.previous_id.id or False cr.execute( "SELECT id FROM account_asset_depreciation_line " "WHERE previous_id = %s" % dl.id) next = cr.fetchone() if next: next_id = next[0] self.write(cr, uid, [next_id], {'previous_id': previous_id}) return super(account_asset_depreciation_line, self).unlink( cr, uid, ids, context=context) def write(self, cr, uid, ids, vals, context=None): if not context: context = {} if isinstance(ids, (int, long)): ids = [ids] for dl in self.browse(cr, uid, ids, context): if vals.keys() == ['move_id'] and not vals['move_id']: # allow to remove an accounting entry via the # 'Delete Move' button on the depreciation lines. if not context.get('unlink_from_asset'): raise orm.except_orm( _('Error!'), _("You are not allowed to remove an accounting entry " "linked to an asset." "\nYou should remove such entries from the asset.")) elif vals.keys() == ['asset_id']: continue elif dl.move_id and not context.get('allow_asset_line_update'): raise orm.except_orm( _('Error!'), _("You cannot change a depreciation line " "with an associated accounting entry.")) elif vals.get('init_entry'): cr.execute( "SELECT id " "FROM account_asset_depreciation_line " "WHERE asset_id = %s AND move_check = TRUE " "AND type = 'depreciate' AND line_date <= %s LIMIT 1", (dl.asset_id.id, dl.line_date)) res = cr.fetchone() if res: raise orm.except_orm( _('Error!'), _("You cannot set the 'Initial Balance Entry' flag " "on a depreciation line " "with prior posted entries.")) elif vals.get('line_date'): cr.execute( "SELECT id " "FROM account_asset_depreciation_line " "WHERE asset_id = %s " "AND (init_entry=TRUE OR move_check=TRUE)" "AND line_date > %s LIMIT 1", (dl.asset_id.id, vals['line_date'])) res = cr.fetchone() if res: raise orm.except_orm( _('Error!'), _("You cannot set the date on a depreciation line " "prior to already posted entries.")) return super(account_asset_depreciation_line, self).write( cr, uid, ids, vals, context) def reload_page(self, cr, uid, asset_id, context=None): return { 'type': 'ir.actions.client', 'tag': 'reload', } def _setup_move_data(self, depreciation_line, depreciation_date, period_id, context): asset = depreciation_line.asset_id move_data = { 'name': asset.name, 'date': depreciation_date, 'ref': depreciation_line.name, 'period_id': period_id, 'journal_id': asset.category_id.journal_id.id, } return move_data def _setup_move_line_data(self, depreciation_line, depreciation_date, period_id, account_id, type, move_id, context): asset = depreciation_line.asset_id amount = depreciation_line.amount analytic_id = False if type == 'depreciation': debit = amount < 0 and -amount or 0.0 credit = amount > 0 and amount or 0.0 elif type == 'expense': debit = amount > 0 and amount or 0.0 credit = amount < 0 and -amount or 0.0 analytic_id = asset.category_id.account_analytic_id.id move_line_data = { 'name': asset.name, 'ref': depreciation_line.name, 'move_id': move_id, 'account_id': account_id, 'credit': credit, 'debit': debit, 'period_id': period_id, 'journal_id': asset.category_id.journal_id.id, 'partner_id': asset.partner_id.id, 'analytic_account_id': analytic_id, 'date': depreciation_date, 'asset_id': asset.id, } return move_line_data def create_move(self, cr, uid, ids, context=None): if context is None: context = {} asset_obj = self.pool.get('account.asset.asset') period_obj = self.pool.get('account.period') move_obj = self.pool.get('account.move') move_line_obj = self.pool.get('account.move.line') currency_obj = self.pool.get('res.currency') created_move_ids = [] asset_ids = [] for line in self.browse(cr, uid, ids, context=context): asset = line.asset_id if asset.method_time == 'year': depreciation_date = context.get('depreciation_date') or \ line.line_date else: depreciation_date = context.get('depreciation_date') or \ time.strftime('%Y-%m-%d') ctx = dict(context, account_period_prefer_normal=True) period_ids = period_obj.find( cr, uid, depreciation_date, context=ctx) period_id = period_ids and period_ids[0] or False move_id = move_obj.create(cr, uid, self._setup_move_data( line, depreciation_date, period_id, context), context=context) depr_acc_id = asset.category_id.account_depreciation_id.id exp_acc_id = asset.category_id.account_expense_depreciation_id.id ctx = dict(context, allow_asset=True) move_line_obj.create(cr, uid, self._setup_move_line_data( line, depreciation_date, period_id, depr_acc_id, 'depreciation', move_id, context), ctx) move_line_obj.create(cr, uid, self._setup_move_line_data( line, depreciation_date, period_id, exp_acc_id, 'expense', move_id, context), ctx) self.write( cr, uid, line.id, {'move_id': move_id}, context={'allow_asset_line_update': True}) created_move_ids.append(move_id) asset_ids.append(asset.id) # we re-evaluate the assets to determine whether we can close them for asset in asset_obj.browse( cr, uid, list(set(asset_ids)), context=context): if currency_obj.is_zero(cr, uid, asset.company_id.currency_id, asset.value_residual): asset.write({'state': 'close'}) if len(ids) == 1 and context.get('create_move_from_button'): return self.reload_page(cr, uid, asset.id, context) return created_move_ids def open_move(self, cr, uid, ids, context=None): for line in self.browse(cr, uid, ids, context=context): return { 'name': _("Journal Entry"), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move', 'view_id': False, 'type': 'ir.actions.act_window', 'context': context, 'nodestroy': True, 'domain': [('id', '=', line.move_id.id)], } def unlink_move(self, cr, uid, ids, context=None): if not context: context = {} move_obj = self.pool.get('account.move') ctx = {'unlink_from_asset': True} for line in self.browse(cr, uid, ids, context=context): move = line.move_id if move.state == 'posted': move_obj.button_cancel(cr, uid, [move.id], context=context) move_obj.unlink(cr, uid, [move.id], context=ctx) # trigger store function self.write(cr, uid, [line.id], {'move_id': False}, context=ctx) if line.parent_state == 'close': line.asset_id.write({'state': 'open'}) if len(ids) == 1: return self.reload_page( cr, uid, line.asset_id.id, context) elif line.parent_state == 'removed' and line.type == 'remove': line.asset_id.write({'state': 'close'}) self.unlink(cr, uid, [line.id]) if len(ids) == 1: return self.reload_page(cr, uid, line.asset_id.id, context) return True class account_move_line(orm.Model): _inherit = 'account.move.line' _columns = { 'asset_id': fields.many2one( 'account.asset.asset', 'Asset', ondelete="restrict"), } class account_asset_history(orm.Model): _name = 'account.asset.history' _description = 'Asset history' _columns = { 'name': fields.char('History name', size=64, select=1), 'user_id': fields.many2one('res.users', 'User', required=True), 'date': fields.date('Date', required=True), 'asset_id': fields.many2one( 'account.asset.asset', 'Asset', required=True, ondelete='cascade'), 'method_time': fields.selection([ ('year', 'Number of Years'), # ('number','Number of Depreciations'), # ('end','Ending Date'), ], 'Time Method', required=True), 'method_number': fields.integer( 'Number of Years', help="The number of years needed to depreciate your asset"), 'method_period': fields.selection([ ('month', 'Month'), ('quarter', 'Quarter'), ('year', 'Year'), ], 'Period Length', help="Period length for the depreciation accounting entries"), 'method_end': fields.date('Ending date'), 'note': fields.text('Note'), } _order = 'date desc' _defaults = { 'date': lambda *args: time.strftime('%Y-%m-%d'), 'user_id': lambda self, cr, uid, ctx: uid }