diff --git a/account_asset_management/README.rst b/account_asset_management/README.rst index 6d9bc3a4..dad7db1d 100644 --- a/account_asset_management/README.rst +++ b/account_asset_management/README.rst @@ -1,8 +1,10 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :alt: License: AGPL-3 +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 -Financial asset management. -=========================== +========================== +Financial asset management +========================== This Module manages the assets owned by a company. It will keep track of depreciation's occurred on those assets. And it allows to create @@ -24,6 +26,13 @@ Configuration It is recommended to configure your Purchase Journal with "Group Invoice Lines" to avoid the creation of separate assets per Supplier Invoice Line. +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/92/10.0 + Known issues ============ @@ -32,10 +41,10 @@ The module in NOT compatible with the standard account_asset module. Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed feedback -`here `_. +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. Credits ======= @@ -47,6 +56,7 @@ Contributors - Frédéric Clementi (camptocamp) - Florian Dacosta (Akretion) - Stéphane Bidoul (Acsone) +- Adrien Peiffer (Acsone) Maintainer ---------- @@ -61,4 +71,4 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -To contribute to this module, please visit http://odoo-community.org. \ No newline at end of file +To contribute to this module, please visit http://odoo-community.org. diff --git a/account_asset_management/__init__.py b/account_asset_management/__init__.py index 09dc24a8..408a6001 100644 --- a/account_asset_management/__init__.py +++ b/account_asset_management/__init__.py @@ -1,33 +1,3 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# -# Copyright (C) 2004-2009 Tiny SPRL (). -# 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 . -# -############################################################################## - -from . import account_asset -from . import account_asset_invoice -from . import account -from . import account_move +# -*- coding: utf-8 -*- +from . import models from . import wizard -from . import report -from . import res_config - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_asset_management/__manifest__.py b/account_asset_management/__manifest__.py new file mode 100644 index 00000000..1d1666d8 --- /dev/null +++ b/account_asset_management/__manifest__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright 2009-2018 Noviat +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Assets Management', + 'version': '10.0.3.1.0', + 'license': 'AGPL-3', + 'depends': [ + 'account_fiscal_year', + ], + 'conflicts': ['account_asset'], + 'author': "Noviat,Odoo Community Association (OCA)", + 'website': 'http://www.noviat.com', + 'category': 'Accounting & Finance', + 'sequence': 32, + 'data': [ + 'security/account_asset_security.xml', + 'security/ir.model.access.csv', + 'wizard/account_asset_compute.xml', + 'wizard/account_asset_remove.xml', + 'views/account_account.xml', + 'views/account_asset.xml', + 'views/account_asset_profile.xml', + 'views/account_config_settings.xml', + 'views/account_invoice.xml', + 'views/account_invoice_line.xml', + 'views/account_move.xml', + 'views/account_move_line.xml', + 'views/menuitem.xml', + ], + 'auto_install': False, + 'installable': True, + 'application': True, +} diff --git a/account_asset_management/__openerp__.py b/account_asset_management/__openerp__.py deleted file mode 100644 index 1c6386bb..00000000 --- a/account_asset_management/__openerp__.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# -# Copyright (C) 2010-2012 OpenERP s.a. (). -# Copyright (c) 2014-2015 Noviat nv/sa (www.noviat.com). -# -# 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 . -# -############################################################################## -{ - 'name': 'Assets Management', - 'version': '8.0.2.6.0', - 'depends': ['account'], - 'conflicts': ['account_asset'], - 'author': "OpenERP & Noviat,Odoo Community Association (OCA)", - 'website': 'http://www.noviat.com', - 'category': 'Accounting & Finance', - 'sequence': 32, - 'demo': ['account_asset_demo.xml'], - 'test': [ - 'test/account_asset_demo.yml', - 'test/account_asset.yml', - ], - 'data': [ - 'security/account_asset_security.xml', - 'security/ir.model.access.csv', - 'wizard/account_asset_change_duration_view.xml', - 'wizard/wizard_asset_compute_view.xml', - 'wizard/account_asset_remove_view.xml', - 'account_asset_view.xml', - 'account_view.xml', - 'account_asset_invoice_view.xml', - 'report/account_asset_report_view.xml', - 'res_config_view.xml', - ], - 'auto_install': False, - 'installable': True, - 'application': True, -} diff --git a/account_asset_management/account.py b/account_asset_management/account.py deleted file mode 100644 index 4ab1b443..00000000 --- a/account_asset_management/account.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- 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 -from openerp.osv import orm, fields -from openerp import tools -from openerp import SUPERUSER_ID - - -class account_account(orm.Model): - _inherit = 'account.account' - - _columns = { - 'asset_category_id': fields.many2one( - 'account.asset.category', 'Asset Category', - help="Default Asset Category when creating invoice lines " - "with this account."), - } - - def _check_asset_categ(self, cr, uid, ids, context=None): - if not context: - context = {} - for account in self.browse(cr, uid, ids, context=context): - if account.asset_category_id and \ - account.asset_category_id.account_asset_id != account: - return False - return True - - _constraints = [ - (_check_asset_categ, - "The Asset Account defined in the Asset Category " - "must be equal to the account.", - ['asset_categ_id']), - ] - - -class account_fiscalyear(orm.Model): - _inherit = 'account.fiscalyear' - - def create(self, cr, uid, vals, context=None): - # To DO : - # change logic to avoid table recompute overhead - # when a regular (duration = 1 year) new FY is created - recompute_obj = self.pool.get('account.asset.recompute.trigger') - user_obj = self.pool.get('res.users') - recompute_vals = { - 'reason': 'creation of fiscalyear %s' % vals.get('code'), - 'company_id': - vals.get('company_id') or - user_obj.browse(cr, uid, uid, context).company_id.id, - 'date_trigger': time.strftime( - tools.DEFAULT_SERVER_DATETIME_FORMAT), - 'state': 'open', - } - recompute_obj.create( - cr, SUPERUSER_ID, recompute_vals, context=context) - return super(account_fiscalyear, self).create( - cr, uid, vals, context=context) - - def write(self, cr, uid, ids, vals, context=None): - if isinstance(ids, (int, long)): - ids = [ids] - if vals.get('date_start') or vals.get('date_stop'): - recompute_obj = self.pool.get('account.asset.recompute.trigger') - fy_datas = self.read(cr, uid, ids, ['code', 'company_id']) - for fy_data in fy_datas: - recompute_vals = { - 'reason': - 'duration change of fiscalyear %s' % fy_data['code'], - 'company_id': fy_data['company_id'][0], - 'date_trigger': - time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT), - 'state': 'open', - } - recompute_obj.create( - cr, SUPERUSER_ID, recompute_vals, context=context) - return super(account_fiscalyear, self).write( - cr, uid, ids, vals, context=context) diff --git a/account_asset_management/account_asset.py b/account_asset_management/account_asset.py deleted file mode 100644 index 2dfed445..00000000 --- a/account_asset_management/account_asset.py +++ /dev/null @@ -1,1721 +0,0 @@ -# -*- 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', - domain=[('type', '!=', 'view'), - ('state', 'not in', ('close', 'cancelled'))]), - '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,), - 'account_analytic_id': fields.many2one( - 'account.analytic.account', 'Analytic account', - domain=[('type', '!=', 'view'), - ('state', 'not in', ('close', 'cancelled'))]), - } - - _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, - 'account_analytic_id': category_obj.account_analytic_id.id, - } - 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 _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.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'}) - 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'}) - elif line.parent_state == 'removed' and line.type == 'remove': - line.asset_id.write({'state': 'close'}) - self.unlink(cr, uid, [line.id]) - 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 - } diff --git a/account_asset_management/account_asset_invoice.py b/account_asset_management/account_asset_invoice.py deleted file mode 100644 index e44d838a..00000000 --- a/account_asset_management/account_asset_invoice.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# -# Copyright (C) 2010-2012 OpenERP s.a. (). -# Copyright (c) 2014-2015 Noviat nv/sa (www.noviat.com). -# -# 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 . -# -############################################################################## - -from openerp.osv import fields, orm -import logging -_logger = logging.getLogger(__name__) - - -class account_invoice(orm.Model): - _inherit = 'account.invoice' - - def action_number(self, cr, uid, ids, context=None): - super(account_invoice, self).action_number(cr, uid, ids, context) - asset_obj = self.pool['account.asset.asset'] - asset_line_obj = self.pool['account.asset.depreciation.line'] - for inv in self.browse(cr, uid, ids): - move = inv.move_id - assets = [aml.asset_id for aml in - filter(lambda x: x.asset_id, move.line_id)] - ctx = {'create_asset_from_move_line': True} - for asset in assets: - asset_obj.write( - cr, uid, [asset.id], - {'code': inv.internal_number}, context=ctx) - asset_line_name = asset_obj._get_depreciation_entry_name( - cr, uid, asset, 0) - asset_line_obj.write( - cr, uid, [asset.depreciation_line_ids[0].id], - {'name': asset_line_name}, - context={'allow_asset_line_update': True}) - return True - - def action_cancel(self, cr, uid, ids, context=None): - assets = [] - for inv in self.browse(cr, uid, ids): - move = inv.move_id - assets = move and \ - [aml.asset_id for aml in - filter(lambda x: x.asset_id, move.line_id)] - super(account_invoice, self).action_cancel( - cr, uid, ids, context=context) - if assets: - asset_obj = self.pool.get('account.asset.asset') - asset_obj.unlink(cr, uid, [x.id for x in assets]) - return True - - def line_get_convert(self, cr, uid, x, part, date, context=None): - res = super(account_invoice, self).line_get_convert( - cr, uid, x, part, date, context=context) - if x.get('asset_category_id'): - # skip empty debit/credit - if res.get('debit') or res.get('credit'): - res['asset_category_id'] = x['asset_category_id'] - return res - - def inv_line_characteristic_hashcode(self, invoice_line): - res = super(account_invoice, self).inv_line_characteristic_hashcode( - invoice_line) - res += '-%s' % invoice_line.get('asset_category_id', 'False') - return res - - -class account_invoice_line(orm.Model): - _inherit = 'account.invoice.line' - - _columns = { - 'asset_category_id': fields.many2one( - 'account.asset.category', 'Asset Category'), - 'asset_id': fields.many2one( - 'account.asset.asset', 'Asset', - domain=[('type', '=', 'normal'), - ('state', 'in', ['open', 'close'])], - help="Complete this field when selling an asset " - "in order to facilitate the creation of the " - "asset removal accounting entries via the " - "asset 'Removal' button"), - } - - def onchange_account_id(self, cr, uid, ids, product_id, - partner_id, inv_type, fposition_id, account_id): - res = super(account_invoice_line, self).onchange_account_id( - cr, uid, ids, product_id, - partner_id, inv_type, fposition_id, account_id) - if account_id: - asset_category = self.pool['account.account'].browse( - cr, uid, account_id).asset_category_id - if asset_category: - vals = {'asset_category_id': asset_category.id} - if 'value' not in res: - res['value'] = vals - else: - res['value'].update(vals) - return res - - def move_line_get_item(self, cr, uid, line, context=None): - res = super(account_invoice_line, self).move_line_get_item( - cr, uid, line, context) - if line.asset_category_id: - res['asset_category_id'] = line.asset_category_id.id - return res diff --git a/account_asset_management/account_asset_invoice_view.xml b/account_asset_management/account_asset_invoice_view.xml deleted file mode 100644 index 538a8b5d..00000000 --- a/account_asset_management/account_asset_invoice_view.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - account.invoice.form.add.asset_category - account.invoice.line - - - - - - - - - - - account.invoice.supplier.form - account.invoice - - - - - - - - - - account.invoice.customer.form - account.invoice - - - - - - - - - - diff --git a/account_asset_management/account_asset_view.xml b/account_asset_management/account_asset_view.xml deleted file mode 100644 index 8ba46de5..00000000 --- a/account_asset_management/account_asset_view.xml +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - account.asset.category.form - account.asset.category - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - account.asset.category.tree - account.asset.category - - - - - - - - - - - - account.asset.category.search - account.asset.category - - - - - - - - - - - - account.asset.asset.form - account.asset.asset - -
-
-
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - -