diff --git a/.travis.yml b/.travis.yml index dd41da82e..a7db1591b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ virtualenv: system_site_packages: true install: + - git clone https://github.com/OCA/reporting-engine ${HOME}/reporting-engine -b ${VERSION} - git clone https://github.com/oca/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools #- git clone https://github.com/OCA/connector $HOME/connector -b ${VERSION} - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} diff --git a/__unported__/account_cancel_invoice_check_payment_order/i18n/de.po b/__unported__/account_cancel_invoice_check_payment_order/i18n/de.po index 6afd73f83..2e0dacdae 100644 --- a/__unported__/account_cancel_invoice_check_payment_order/i18n/de.po +++ b/__unported__/account_cancel_invoice_check_payment_order/i18n/de.po @@ -8,28 +8,29 @@ msgstr "" "Project-Id-Version: account-financial-tools\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2013-10-18 17:46+0000\n" -"PO-Revision-Date: 2014-05-13 10:22+0000\n" -"Last-Translator: FULL NAME \n" +"PO-Revision-Date: 2015-01-05 20:07+0100\n" +"Last-Translator: Rudolf Schnapka \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2014-06-12 06:31+0000\n" -"X-Generator: Launchpad (build 17041)\n" +"X-Generator: Poedit 1.5.4\n" +"Language: de\n" #. module: account_cancel_invoice_check_payment_order #: code:addons/account_cancel_invoice_check_payment_order/account_invoice.py:48 #, python-format msgid "Error !" -msgstr "" +msgstr "Fehler!" #. module: account_cancel_invoice_check_payment_order #: code:addons/account_cancel_invoice_check_payment_order/account_invoice.py:51 #, python-format msgid "Invoice already imported in the payment order (%s) at %s on line %s" -msgstr "" +msgstr "Diese Rechnung wurde bereits zur Zahlanweisung (%s) zum %s an Position %s importiert" #. module: account_cancel_invoice_check_payment_order #: model:ir.model,name:account_cancel_invoice_check_payment_order.model_account_invoice msgid "Invoice" -msgstr "" +msgstr "Rechnung" diff --git a/__unported__/account_check_deposit/account_deposit_view.xml b/__unported__/account_check_deposit/account_deposit_view.xml index f4b61143b..3c09da374 100644 --- a/__unported__/account_check_deposit/account_deposit_view.xml +++ b/__unported__/account_check_deposit/account_deposit_view.xml @@ -68,7 +68,7 @@ widget="many2many" domain="[('reconcile_id', '=', False), ('debit', '>', 0), - ('journal_id', '=', journal_id), + ('check_deposit_id', '=', False), ('currency_id', '=', currency_none_same_company_id), ('account_id', '=', journal_default_account_id)]" context="{'currency': currency_id, diff --git a/__unported__/account_tax_update/__openerp__.py b/__unported__/account_tax_update/__openerp__.py index 9bf1b6fa4..355306a1f 100644 --- a/__unported__/account_tax_update/__openerp__.py +++ b/__unported__/account_tax_update/__openerp__.py @@ -22,7 +22,7 @@ { "name": "Update tax wizard", "version": "1.0.44", - "author": "Therp BV/Camptocamp SA", + "author": "Therp BV, Camptocamp SA", "category": 'Base', 'complexity': "normal", "description": """ diff --git a/__unported__/account_tax_update/i18n/account_tax_update.po b/__unported__/account_tax_update/i18n/account_tax_update.po deleted file mode 100644 index 0b659060b..000000000 --- a/__unported__/account_tax_update/i18n/account_tax_update.po +++ /dev/null @@ -1,274 +0,0 @@ -# Translation of OpenERP Server. -# This file contains the translation of the following modules: -# * account_tax_update -# -msgid "" -msgstr "" -"Project-Id-Version: OpenERP Server 7.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-11-11 15:47+0000\n" -"PO-Revision-Date: 2013-11-11 15:47+0000\n" -"Last-Translator: <>\n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: account_tax_update -#: view:account.update.tax.select_taxes:0 -msgid "Add the taxes that need to be replaced here. Any parent or child taxes will be added automatically when you close this window." -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config.line,target_tax_description:0 -msgid "New tax code" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -#: field:account.update.tax.config,log:0 -msgid "Log" -msgstr "" - -#. module: account_tax_update -#: selection:account.update.tax.config,state:0 -msgid "Confirm" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "When you are done configuring the new tax amounts, click here to create the new taxes and a mapping between the new and old taxes" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "Set legacy purchase taxes inactive" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "Add sale taxes" -msgstr "" - -#. module: account_tax_update -#: model:ir.actions.act_window,name:account_tax_update.action_update_tax_config -#: model:ir.ui.menu,name:account_tax_update.menu_update_tax_config -msgid "Update tax wizard" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,state:0 -#: field:account.update.tax.config.line,state:0 -msgid "State" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config.line,source_tax_description:0 -msgid "Old tax code" -msgstr "" - -#. module: account_tax_update -#: selection:account.update.tax.config,state:0 -msgid "Draft" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config.line,target_tax_id:0 -msgid "Target tax" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,sale_set_defaults:0 -msgid "Sales tax defaults have been set" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "Replace sales tax defaults" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.select_taxes:0 -msgid "Save" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,default_amount:0 -msgid "Default new amount" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -#: field:account.update.tax.config,purchase_line_ids:0 -msgid "Purchase taxes" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "Set legacy sales taxes inactive" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,purchase_set_inactive:0 -msgid "Purchase taxes have been set to inactive" -msgstr "" - -#. module: account_tax_update -#: model:ir.actions.act_window,help:account_tax_update.action_update_tax_config -msgid "For every tax increase, you can create a new configuration. Select sales and purchase taxes to adapt. The selected taxes will be duplicated. The old tax names will be prefixed with a 'legacy' tag. Change the new tax names and codes in the window below to reflect the amount change. The new taxes then become available to select manually on sale or purchase order lines. You may also want to change the names of the associated tax codes, or reassign them between the taxes. At specific times during the transition, you can replace the default sales and purchase taxes in the system. Eventually you can hide the obsolete taxes by making them inactive. Until then, you can still select the old taxes manually" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config.line,amount_old:0 -msgid "Old amount" -msgstr "" - -#. module: account_tax_update -#: model:ir.model,name:account_tax_update.model_account_update_tax_config_line -msgid "Tax update configuration lines" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "Replace purchase tax defaults" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config.line,purchase_config_id:0 -#: field:account.update.tax.config.line,sale_config_id:0 -#: field:account.update.tax.select_taxes,config_id:0 -msgid "Configuration" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "Create tax mappings" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,duplicate_tax_code:0 -msgid "Duplicate Tax code linked" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,name:0 -msgid "Legacy taxes prefix" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,purchase_set_defaults:0 -msgid "Purchase tax defaults have been set" -msgstr "" - -#. module: account_tax_update -#: code:addons/account_tax_update/model/update_tax_config.py:105 -#: code:addons/account_tax_update/model/update_tax_config.py:281 -#: code:addons/account_tax_update/model/update_tax_config.py:395 -#, python-format -msgid "Can not detect tax use type" -msgstr "" - -#. module: account_tax_update -#: view:account.tax:0 -#: view:account.update.tax.config:0 -#: view:account.update.tax.config.line:0 -#: view:account.update.tax.select_taxes:0 -#: field:account.update.tax.select_taxes,tax_ids:0 -msgid "Taxes" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.select_taxes,covered_tax_ids:0 -msgid "Covered taxes" -msgstr "" - -#. module: account_tax_update -#: help:account.update.tax.config,name:0 -msgid "The processed taxes will be marked with this name" -msgstr "" - -#. module: account_tax_update -#: code:addons/account_tax_update/model/update_tax_config.py:104 -#: code:addons/account_tax_update/model/update_tax_config.py:280 -#: code:addons/account_tax_update/model/update_tax_config.py:394 -#, python-format -msgid "Error" -msgstr "" - -#. module: account_tax_update -#: selection:account.update.tax.config,state:0 -msgid "Purchase updated" -msgstr "" - -#. module: account_tax_update -#: selection:account.update.tax.config,state:0 -msgid "Sales updated" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config.line,source_tax_id:0 -msgid "Source tax" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -msgid "Add purchase taxes" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config.line,amount_new:0 -msgid "New amount" -msgstr "" - -#. module: account_tax_update -#: model:ir.model,name:account_tax_update.model_account_update_tax_config -msgid "Update taxes" -msgstr "" - -#. module: account_tax_update -#: model:ir.model,name:account_tax_update.model_account_tax -msgid "Tax" -msgstr "" - -#. module: account_tax_update -#: sql_constraint:account.update.tax.config:0 -msgid "Name must be unique." -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.config:0 -#: field:account.update.tax.config,sale_line_ids:0 -msgid "Sales taxes" -msgstr "" - -#. module: account_tax_update -#: selection:account.update.tax.config,state:0 -msgid "Done" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.select_taxes,type_tax_use:0 -msgid "Type tax use" -msgstr "" - -#. module: account_tax_update -#: view:account.update.tax.select_taxes:0 -msgid "Cancel" -msgstr "" - -#. module: account_tax_update -#: model:ir.model,name:account_tax_update.model_account_update_tax_select_taxes -msgid "Select the taxes to be updated" -msgstr "" - -#. module: account_tax_update -#: field:account.update.tax.config,sale_set_inactive:0 -msgid "Sales taxes have been set to inactive" -msgstr "" - -#. module: account_tax_update -#: help:account.update.tax.config,default_amount:0 -msgid "Although it is possible to specify a distinct new amount per tax, you can set the default value here." -msgstr "" - diff --git a/account_asset_management/__init__.py b/account_asset_management/__init__.py new file mode 100644 index 000000000..09dc24a85 --- /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 000000000..b6a23442b --- /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', + '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': True, + '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 000000000..4ab1b4438 --- /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 000000000..de12154a0 --- /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 000000000..589160832 --- /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 000000000..9a6066f13 --- /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 000000000..5704c487a --- /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 000000000..d23d8e063 --- /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 + +
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +