From 99560a0cb9ccb11273dc26f1e62a248afc5c8534 Mon Sep 17 00:00:00 2001 From: Leonardo Pistone Date: Thu, 12 Feb 2015 11:51:18 +0100 Subject: [PATCH 001/189] move account_asset_management* out of __unported__ add asset management modules asset mgt update redo synch asset mgt with recent V7 changes --- account_asset_management/__init__.py | 33 + account_asset_management/__openerp__.py | 82 + account_asset_management/account.py | 98 + account_asset_management/account_asset.py | 1727 +++++++++++++++++ .../account_asset_demo.xml | 105 + .../account_asset_invoice.py | 122 ++ .../account_asset_invoice_view.xml | 39 + .../account_asset_view.xml | 385 ++++ account_asset_management/account_move.py | 168 ++ account_asset_management/account_view.xml | 65 + .../doc/account_asset_migration_script.txt | 319 +++ account_asset_management/doc/changelog.rst | 111 ++ account_asset_management/doc/index.rst | 10 + .../i18n/account_asset.pot | 735 +++++++ account_asset_management/i18n/ar.po | 770 ++++++++ account_asset_management/i18n/bs.po | 775 ++++++++ account_asset_management/i18n/ca.po | 758 ++++++++ account_asset_management/i18n/cs.po | 771 ++++++++ account_asset_management/i18n/da.po | 758 ++++++++ account_asset_management/i18n/de.po | 799 ++++++++ account_asset_management/i18n/en_GB.po | 793 ++++++++ account_asset_management/i18n/es.po | 793 ++++++++ account_asset_management/i18n/es_AR.po | 758 ++++++++ account_asset_management/i18n/es_CR.po | 777 ++++++++ account_asset_management/i18n/es_EC.po | 777 ++++++++ account_asset_management/i18n/es_MX.po | 791 ++++++++ account_asset_management/i18n/es_VE.po | 537 +++++ account_asset_management/i18n/et.po | 758 ++++++++ account_asset_management/i18n/fi.po | 758 ++++++++ account_asset_management/i18n/fr.po | 798 ++++++++ account_asset_management/i18n/fr_BE.po | 526 +++++ account_asset_management/i18n/gu.po | 758 ++++++++ account_asset_management/i18n/hr.po | 758 ++++++++ account_asset_management/i18n/hu.po | 758 ++++++++ account_asset_management/i18n/id.po | 758 ++++++++ account_asset_management/i18n/it.po | 798 ++++++++ account_asset_management/i18n/ja.po | 764 ++++++++ account_asset_management/i18n/ko.po | 758 ++++++++ account_asset_management/i18n/lt.po | 778 ++++++++ account_asset_management/i18n/mk.po | 797 ++++++++ account_asset_management/i18n/mn.po | 788 ++++++++ account_asset_management/i18n/nb.po | 776 ++++++++ account_asset_management/i18n/nl.po | 799 ++++++++ account_asset_management/i18n/nl_BE.po | 789 ++++++++ account_asset_management/i18n/pl.po | 758 ++++++++ account_asset_management/i18n/pt.po | 778 ++++++++ account_asset_management/i18n/pt_BR.po | 793 ++++++++ account_asset_management/i18n/ro.po | 797 ++++++++ account_asset_management/i18n/ru.po | 763 ++++++++ account_asset_management/i18n/sl.po | 778 ++++++++ account_asset_management/i18n/sr@latin.po | 758 ++++++++ account_asset_management/i18n/sv.po | 776 ++++++++ account_asset_management/i18n/th.po | 758 ++++++++ account_asset_management/i18n/tr.po | 792 ++++++++ account_asset_management/i18n/vi.po | 758 ++++++++ account_asset_management/i18n/zh_CN.po | 764 ++++++++ account_asset_management/i18n/zh_TW.po | 764 ++++++++ account_asset_management/report/__init__.py | 26 + .../report/account_asset_report.py | 93 + .../report/account_asset_report_view.xml | 92 + account_asset_management/res_config.py | 37 + account_asset_management/res_config_view.xml | 20 + .../security/account_asset_security.xml | 20 + .../security/ir.model.access.csv | 17 + .../static/description/icon.png | Bin 0 -> 6390 bytes .../test/account_asset.yml | 25 + .../test/account_asset_demo.yml | 9 + account_asset_management/tests/__init__.py | 28 + .../tests/test_account_asset_management.py | 213 ++ account_asset_management/wizard/__init__.py | 28 + .../wizard/account_asset_change_duration.py | 125 ++ .../account_asset_change_duration_view.xml | 45 + .../wizard/account_asset_remove.py | 354 ++++ .../wizard/account_asset_remove_view.xml | 36 + .../wizard/wizard_asset_compute.py | 73 + .../wizard/wizard_asset_compute_view.xml | 35 + 76 files changed, 38088 insertions(+) create mode 100644 account_asset_management/__init__.py create mode 100644 account_asset_management/__openerp__.py create mode 100644 account_asset_management/account.py create mode 100644 account_asset_management/account_asset.py create mode 100644 account_asset_management/account_asset_demo.xml create mode 100644 account_asset_management/account_asset_invoice.py create mode 100644 account_asset_management/account_asset_invoice_view.xml create mode 100644 account_asset_management/account_asset_view.xml create mode 100644 account_asset_management/account_move.py create mode 100644 account_asset_management/account_view.xml create mode 100644 account_asset_management/doc/account_asset_migration_script.txt create mode 100644 account_asset_management/doc/changelog.rst create mode 100644 account_asset_management/doc/index.rst create mode 100644 account_asset_management/i18n/account_asset.pot create mode 100644 account_asset_management/i18n/ar.po create mode 100644 account_asset_management/i18n/bs.po create mode 100644 account_asset_management/i18n/ca.po create mode 100644 account_asset_management/i18n/cs.po create mode 100644 account_asset_management/i18n/da.po create mode 100644 account_asset_management/i18n/de.po create mode 100644 account_asset_management/i18n/en_GB.po create mode 100644 account_asset_management/i18n/es.po create mode 100644 account_asset_management/i18n/es_AR.po create mode 100644 account_asset_management/i18n/es_CR.po create mode 100644 account_asset_management/i18n/es_EC.po create mode 100644 account_asset_management/i18n/es_MX.po create mode 100644 account_asset_management/i18n/es_VE.po create mode 100644 account_asset_management/i18n/et.po create mode 100644 account_asset_management/i18n/fi.po create mode 100644 account_asset_management/i18n/fr.po create mode 100644 account_asset_management/i18n/fr_BE.po create mode 100644 account_asset_management/i18n/gu.po create mode 100644 account_asset_management/i18n/hr.po create mode 100644 account_asset_management/i18n/hu.po create mode 100644 account_asset_management/i18n/id.po create mode 100644 account_asset_management/i18n/it.po create mode 100644 account_asset_management/i18n/ja.po create mode 100644 account_asset_management/i18n/ko.po create mode 100644 account_asset_management/i18n/lt.po create mode 100644 account_asset_management/i18n/mk.po create mode 100644 account_asset_management/i18n/mn.po create mode 100644 account_asset_management/i18n/nb.po create mode 100644 account_asset_management/i18n/nl.po create mode 100644 account_asset_management/i18n/nl_BE.po create mode 100644 account_asset_management/i18n/pl.po create mode 100644 account_asset_management/i18n/pt.po create mode 100644 account_asset_management/i18n/pt_BR.po create mode 100644 account_asset_management/i18n/ro.po create mode 100644 account_asset_management/i18n/ru.po create mode 100644 account_asset_management/i18n/sl.po create mode 100644 account_asset_management/i18n/sr@latin.po create mode 100644 account_asset_management/i18n/sv.po create mode 100644 account_asset_management/i18n/th.po create mode 100644 account_asset_management/i18n/tr.po create mode 100644 account_asset_management/i18n/vi.po create mode 100644 account_asset_management/i18n/zh_CN.po create mode 100644 account_asset_management/i18n/zh_TW.po create mode 100644 account_asset_management/report/__init__.py create mode 100644 account_asset_management/report/account_asset_report.py create mode 100644 account_asset_management/report/account_asset_report_view.xml create mode 100644 account_asset_management/res_config.py create mode 100644 account_asset_management/res_config_view.xml create mode 100644 account_asset_management/security/account_asset_security.xml create mode 100644 account_asset_management/security/ir.model.access.csv create mode 100644 account_asset_management/static/description/icon.png create mode 100644 account_asset_management/test/account_asset.yml create mode 100644 account_asset_management/test/account_asset_demo.yml create mode 100644 account_asset_management/tests/__init__.py create mode 100644 account_asset_management/tests/test_account_asset_management.py create mode 100644 account_asset_management/wizard/__init__.py create mode 100644 account_asset_management/wizard/account_asset_change_duration.py create mode 100644 account_asset_management/wizard/account_asset_change_duration_view.xml create mode 100644 account_asset_management/wizard/account_asset_remove.py create mode 100644 account_asset_management/wizard/account_asset_remove_view.xml create mode 100644 account_asset_management/wizard/wizard_asset_compute.py create mode 100644 account_asset_management/wizard/wizard_asset_compute_view.xml diff --git a/account_asset_management/__init__.py b/account_asset_management/__init__.py new file mode 100644 index 00000000..09dc24a8 --- /dev/null +++ b/account_asset_management/__init__.py @@ -0,0 +1,33 @@ +# -*- 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 +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/__openerp__.py b/account_asset_management/__openerp__.py new file mode 100644 index 00000000..f947f4c3 --- /dev/null +++ b/account_asset_management/__openerp__.py @@ -0,0 +1,82 @@ +# -*- 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 . +# +############################################################################## +{ + 'name': 'Assets Management', + 'version': '2.4', + 'depends': ['account'], + 'conflicts': ['account_asset'], + 'author': "OpenERP & Noviat,Odoo Community Association (OCA)", + 'description': """ +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 +accounting entries from the depreciation lines. + +The full asset life-cycle is managed (from asset creation to asset removal). + +Assets can be created manually as well as automatically +(via the creation of an accounting entry on the asset account). + +Excel based reporting is available via the 'account_asset_management_xls' +module (cf. http://odoo.apps.com). + +The module contains a large number of functional enhancements compared to +the standard account_asset module from OpenERP/Odoo. + +The module in NOT compatible with the standard account_asset module. + +Contributors +------------ +- OpenERP SA +- Luc De Meyer (Noviat) +- Frédéric Clementi (camptocamp) +- Florian Dacosta (Akretion) +- Stéphane Bidoul (Acsone) + """, + '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': False, + 'application': True, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_asset_management/account.py b/account_asset_management/account.py new file mode 100644 index 00000000..4ab1b443 --- /dev/null +++ b/account_asset_management/account.py @@ -0,0 +1,98 @@ +# -*- 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 new file mode 100644 index 00000000..de12154a --- /dev/null +++ b/account_asset_management/account_asset.py @@ -0,0 +1,1727 @@ +# -*- 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 + } diff --git a/account_asset_management/account_asset_demo.xml b/account_asset_management/account_asset_demo.xml new file mode 100644 index 00000000..58916083 --- /dev/null +++ b/account_asset_management/account_asset_demo.xml @@ -0,0 +1,105 @@ + + + + + + + + view + open + Financial Assets + + + + + view + open + ICT + + + + + + view + open + Vehicles + + + + + + + + + + + + + Hardware - 3 Years + year + + year + + + + + + + + + Cars - 5 Years + year + + year + + + + + + + + + draft + year + + year + + Laptop + PI00101 + + + + + + + draft + year + + year + CEO's Car + + + + + + + diff --git a/account_asset_management/account_asset_invoice.py b/account_asset_management/account_asset_invoice.py new file mode 100644 index 00000000..9a6066f1 --- /dev/null +++ b/account_asset_management/account_asset_invoice.py @@ -0,0 +1,122 @@ +# -*- 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 . +# +############################################################################## + +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.get('account.asset.asset') + asset_line_obj = self.pool.get('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, invoice_line): + res = super(account_invoice, self).inv_line_characteristic_hashcode( + invoice, 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.get('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 + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_asset_management/account_asset_invoice_view.xml b/account_asset_management/account_asset_invoice_view.xml new file mode 100644 index 00000000..5704c487 --- /dev/null +++ b/account_asset_management/account_asset_invoice_view.xml @@ -0,0 +1,39 @@ + + + + + + account.invoice.line.form + 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 new file mode 100644 index 00000000..d23d8e06 --- /dev/null +++ b/account_asset_management/account_asset_view.xml @@ -0,0 +1,385 @@ + + + + + + + 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 + +
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +