mirror of
https://github.com/OCA/account-financial-tools.git
synced 2025-02-02 12:47:26 +02:00
performance improvement + early removal
This commit is contained in:
@@ -22,8 +22,9 @@
|
||||
##############################################################################
|
||||
{
|
||||
'name': 'Assets Management',
|
||||
'version': '2.1',
|
||||
'version': '2.2',
|
||||
'depends': ['account'],
|
||||
'conflicts': ['account_asset'],
|
||||
'author': 'OpenERP & Noviat',
|
||||
'description': """
|
||||
Financial asset management.
|
||||
@@ -45,6 +46,14 @@ 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',
|
||||
|
||||
@@ -58,6 +58,9 @@ 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 = {
|
||||
@@ -93,5 +96,3 @@ class account_fiscalyear(orm.Model):
|
||||
cr, SUPERUSER_ID, recompute_vals, context=context)
|
||||
return super(account_fiscalyear, self).write(
|
||||
cr, uid, ids, vals, context=context)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -70,11 +70,23 @@ class account_asset_category(orm.Model):
|
||||
'account_analytic_id': fields.many2one(
|
||||
'account.analytic.account', 'Analytic account'),
|
||||
'account_asset_id': fields.many2one(
|
||||
'account.account', 'Asset Account', required=True),
|
||||
'account.account', 'Asset Account', required=True,
|
||||
domain=[('type', '=', 'other')]),
|
||||
'account_depreciation_id': fields.many2one(
|
||||
'account.account', 'Depreciation Account', required=True),
|
||||
'account.account', 'Depreciation Account', required=True,
|
||||
domain=[('type', '=', 'other')]),
|
||||
'account_expense_depreciation_id': fields.many2one(
|
||||
'account.account', 'Depr. Expense Account', required=True),
|
||||
'account.account', 'Depr. Expense Account', required=True,
|
||||
domain=[('type', '=', 'other')]),
|
||||
'account_plus_value_id': fields.many2one(
|
||||
'account.account', 'Plus-Value Account', required=True,
|
||||
domain=[('type', '=', 'other')]),
|
||||
'account_min_value_id': fields.many2one(
|
||||
'account.account', 'Min-Value Account', required=True,
|
||||
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(
|
||||
@@ -219,6 +231,7 @@ 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):
|
||||
@@ -798,36 +811,11 @@ class account_asset_asset(orm.Model):
|
||||
asset.write({'state': 'open'}, context=context)
|
||||
return True
|
||||
|
||||
def set_to_close(self, cr, uid, ids, context=None):
|
||||
# TO DO:
|
||||
# The 'set_to_close' button is currently removed from the UI
|
||||
# since the state goes automatically to close upon posting of
|
||||
# the last depreciation line.
|
||||
# We could put this button back and launch a wizard to generate
|
||||
# the last depreciation entry and
|
||||
# adapt the depreciation board accordingly.
|
||||
# At this moment, the end user has to manually adjust the
|
||||
# deprecation table and generate the last depreciation entry manually
|
||||
# via the 'create_move' button in the table.
|
||||
for asset in self.browse(cr, uid, ids, context):
|
||||
if asset.value_residual:
|
||||
raise orm.except_orm(
|
||||
_('Operation not allowed!'),
|
||||
_("You cannot close an asset which has not been "
|
||||
"fully depreciated."
|
||||
"\nPlease create the remaining depreciation entry "
|
||||
"via the Depreciation Board."))
|
||||
return self.write(cr, uid, ids, {'state': 'close'}, context=context)
|
||||
|
||||
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:
|
||||
raise orm.except_orm(
|
||||
_('Operation not allowed!'),
|
||||
_("You cannot remove an asset which has not been "
|
||||
"fully depreciated."
|
||||
"\nPlease create the remaining depreciation entry "
|
||||
"via the Depreciation Board."))
|
||||
ctx.update({'early_removal': True})
|
||||
return {
|
||||
'name': _("Generate Asset Removal entries"),
|
||||
'view_type': 'form',
|
||||
@@ -835,7 +823,7 @@ class account_asset_asset(orm.Model):
|
||||
'res_model': 'account.asset.remove',
|
||||
'target': 'new',
|
||||
'type': 'ir.actions.act_window',
|
||||
'context': dict(context, active_ids=ids, active_id=ids[0]),
|
||||
'context': ctx,
|
||||
'nodestroy': True,
|
||||
}
|
||||
|
||||
@@ -868,39 +856,28 @@ class account_asset_asset(orm.Model):
|
||||
res[asset.id] = _value_get(asset)
|
||||
return res
|
||||
|
||||
def _residual_compute(self, cr, uid, asset, context=None):
|
||||
if asset.type == 'view':
|
||||
return 0.0
|
||||
cr.execute(
|
||||
"SELECT COALESCE(SUM(amount),0.0) AS amount "
|
||||
"FROM account_asset_depreciation_line "
|
||||
"WHERE asset_id = %s AND type='depreciate' "
|
||||
"AND (init_entry=TRUE OR move_check=TRUE)",
|
||||
(asset.id,))
|
||||
amount = cr.fetchone()[0]
|
||||
return asset.asset_value - amount
|
||||
|
||||
def _residual(self, cr, uid, ids, name, args, context=None):
|
||||
def _compute_depreciation(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._residual_compute(cr, uid, asset, context)
|
||||
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='depreciate' "
|
||||
"AND (init_entry=TRUE OR move_check=TRUE)",
|
||||
(tuple(child_ids),))
|
||||
value_depreciated = cr.fetchone()[0]
|
||||
else:
|
||||
def _residual_get(record):
|
||||
residual = self._residual_compute(cr, uid, asset, context)
|
||||
for rec in record.child_ids:
|
||||
residual += \
|
||||
rec.type == 'normal' and \
|
||||
self._residual_compute(cr, uid, rec, context) or \
|
||||
_residual_get(rec)
|
||||
return residual
|
||||
res[asset.id] = _residual_get(asset)
|
||||
return res
|
||||
|
||||
def _depreciated(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
for asset in self.browse(cr, uid, ids, context):
|
||||
res[asset.id] = asset.asset_value - asset.value_residual
|
||||
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):
|
||||
@@ -1001,7 +978,7 @@ class account_asset_asset(orm.Model):
|
||||
},
|
||||
help="This amount represent the initial value of the asset."),
|
||||
'value_residual': fields.function(
|
||||
_residual, method=True,
|
||||
_compute_depreciation, method=True, multi='cd',
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
string='Residual Value',
|
||||
store={
|
||||
@@ -1015,7 +992,7 @@ class account_asset_asset(orm.Model):
|
||||
['amount', 'init_entry', 'move_id'], 20),
|
||||
}),
|
||||
'value_depreciated': fields.function(
|
||||
_depreciated, method=True,
|
||||
_compute_depreciation, method=True, multi='cd',
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
string='Depreciated Value',
|
||||
store={
|
||||
@@ -1043,7 +1020,10 @@ class account_asset_asset(orm.Model):
|
||||
'parent_id': fields.many2one(
|
||||
'account.asset.asset', 'Parent Asset', readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
domain=[('type', '=', 'view')]),
|
||||
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(
|
||||
@@ -1233,7 +1213,8 @@ class account_asset_asset(orm.Model):
|
||||
default.update({
|
||||
'depreciation_line_ids': [],
|
||||
'account_move_line_ids': [],
|
||||
'state': 'draft'})
|
||||
'state': 'draft',
|
||||
'history_ids': []})
|
||||
return super(account_asset_asset, self).copy(
|
||||
cr, uid, id, default, context=context)
|
||||
|
||||
@@ -1573,19 +1554,19 @@ class account_asset_depreciation_line(orm.Model):
|
||||
}
|
||||
|
||||
def _setup_move_data(self, depreciation_line, depreciation_date,
|
||||
period_ids, context):
|
||||
period_id, context):
|
||||
asset = depreciation_line.asset_id
|
||||
move_data = {
|
||||
'name': asset.name,
|
||||
'date': depreciation_date,
|
||||
'ref': depreciation_line.name,
|
||||
'period_id': period_ids,
|
||||
'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_ids, account_id, type, move_id, context):
|
||||
period_id, account_id, type, move_id, context):
|
||||
asset = depreciation_line.asset_id
|
||||
amount = depreciation_line.amount
|
||||
analytic_id = False
|
||||
@@ -1603,7 +1584,7 @@ class account_asset_depreciation_line(orm.Model):
|
||||
'account_id': account_id,
|
||||
'credit': credit,
|
||||
'debit': debit,
|
||||
'period_id': period_ids,
|
||||
'period_id': period_id,
|
||||
'journal_id': asset.category_id.journal_id.id,
|
||||
'partner_id': asset.partner_id.id,
|
||||
'analytic_account_id': analytic_id,
|
||||
@@ -1739,5 +1720,3 @@ class account_asset_history(orm.Model):
|
||||
'date': lambda *args: time.strftime('%Y-%m-%d'),
|
||||
'user_id': lambda self, cr, uid, ctx: uid
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -86,6 +86,12 @@ class account_invoice_line(orm.Model):
|
||||
_columns = {
|
||||
'asset_category_id': fields.many2one(
|
||||
'account.asset.category', 'Asset Category'),
|
||||
'asset_id': fields.many2one(
|
||||
'account.asset.asset', 'Asset',
|
||||
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,
|
||||
|
||||
@@ -24,5 +24,16 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_customer_invoice_asset">
|
||||
<field name="name">account.invoice.customer.form</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.invoice_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_line']/tree/field[@name='quantity']" position="before">
|
||||
<field name="asset_id" groups="account.group_account_manager"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
<field name="account_asset_id"/>
|
||||
<field name="account_depreciation_id"/>
|
||||
<field name="account_expense_depreciation_id"/>
|
||||
<field name="account_plus_value_id"/>
|
||||
<field name="account_min_value_id"/>
|
||||
<field name="account_residual_value_id"/>
|
||||
</group>
|
||||
<group string="Depreciation Dates">
|
||||
<field name="method_time" on_change="onchange_method_time(method_time)"/>
|
||||
@@ -76,10 +79,9 @@
|
||||
<header>
|
||||
<button name="validate" states="draft" string="Confirm Asset" type="object" class="oe_highlight"/>
|
||||
<button name="set_to_draft" states="open,close" string="Set to Draft" type="object" groups="account.group_account_manager"/>
|
||||
<!-- <button name="set_to_close" states="open" string="Set to Close" type="object" class="oe_highlight"/> -->
|
||||
<button name="remove" string="Set to Removed" type="object" groups="account.group_account_manager"
|
||||
attrs="{'invisible':['|',('method_time','!=','year'),('state','!=','close')]}"
|
||||
help="Generate the removal entries for a fully depreciated asset."/>
|
||||
<button name="remove" string="Remove" type="object" groups="account.group_account_manager"
|
||||
attrs="{'invisible':['|', ('method_time', '!=', 'year'),('state', 'not in', ['open', 'close'])]}"
|
||||
help="Asset removal."/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,open,close,removed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -194,6 +196,7 @@
|
||||
<page string="History">
|
||||
<field name="account_move_line_ids" readonly="1">
|
||||
<tree colors="red:state == 'draft';black:state == 'valid'" string="Journal Items">
|
||||
<field name="move_id"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="period_id"/>
|
||||
<field name="date"/>
|
||||
@@ -209,7 +212,7 @@
|
||||
<field name="move_id" options='{"no_open":True}'/>
|
||||
<newline/>
|
||||
<field name="name"/>
|
||||
<field name="ref"/>
|
||||
<field name="ref"/>
|
||||
<newline/>
|
||||
<field name="date"/>
|
||||
<field name="period_id" options='{"no_open":True}'/>
|
||||
|
||||
@@ -37,7 +37,8 @@ class account_move(orm.Model):
|
||||
for move_id in ids:
|
||||
depr_ids = depr_obj.search(
|
||||
cr, uid,
|
||||
[('move_id', '=', move_id), ('type', '=', 'depreciate')])
|
||||
[('move_id', '=', move_id),
|
||||
('type', 'in', ['depreciate', 'remove'])])
|
||||
if depr_ids and not context.get('unlink_from_asset'):
|
||||
raise orm.except_orm(
|
||||
_('Error!'),
|
||||
|
||||
@@ -100,4 +100,12 @@ Enhancements/changes made by Noviat (www.noviat.com)
|
||||
|
||||
Enhancements/changes made by Noviat (www.noviat.com)
|
||||
|
||||
- Support assets without depreciation table (e.g. properties that keep their value). Specify 'method_number' = 0 for such assets.
|
||||
- Support assets without depreciation table (e.g. properties that keep their value). Specify 'method_number' = 0 for such assets.
|
||||
|
||||
`V2.2`
|
||||
------
|
||||
|
||||
Enhancements/changes
|
||||
|
||||
- Generation of accounting entries in case of early removal.
|
||||
|
||||
|
||||
28
account_asset_management/tests/__init__.py
Normal file
28
account_asset_management/tests/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# account_asset_management tests
|
||||
#
|
||||
# Copyright (c) 2014 ACSONE SA/NV (acsone.eu).
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from . import test_account_asset_management
|
||||
|
||||
checks = [
|
||||
test_account_asset_management,
|
||||
]
|
||||
118
account_asset_management/tests/test_account_asset_management.py
Normal file
118
account_asset_management/tests/test_account_asset_management.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# account_asset_management tests
|
||||
#
|
||||
# Copyright (c) 2014 ACSONE SA/NV (acsone.eu).
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import openerp.tests.common as common
|
||||
|
||||
|
||||
class TestAssetManagement(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAssetManagement, self).setUp()
|
||||
self.asset_model = self.registry('account.asset.asset')
|
||||
|
||||
def test_1(self):
|
||||
""" Compute depreciation boards and post assets for first year,
|
||||
verify the depreciation values and values in parent assets. """
|
||||
#
|
||||
# first load demo assets and do some sanity checks
|
||||
#
|
||||
ict0 = self.browse_ref('account_asset_management.'
|
||||
'account_asset_asset_ict0')
|
||||
self.assertEquals(ict0.state, 'draft')
|
||||
self.assertEquals(ict0.purchase_value, 1500)
|
||||
self.assertEquals(ict0.salvage_value, 0)
|
||||
self.assertEquals(ict0.asset_value, 1500)
|
||||
self.assertEquals(len(ict0.depreciation_line_ids), 1)
|
||||
vehicle0 = self.browse_ref('account_asset_management.'
|
||||
'account_asset_asset_vehicle0')
|
||||
self.assertEquals(vehicle0.state, 'draft')
|
||||
self.assertEquals(vehicle0.purchase_value, 12000)
|
||||
self.assertEquals(vehicle0.salvage_value, 2000)
|
||||
self.assertEquals(vehicle0.asset_value, 10000)
|
||||
self.assertEquals(len(vehicle0.depreciation_line_ids), 1)
|
||||
ict = self.browse_ref('account_asset_management.'
|
||||
'account_asset_view_ict')
|
||||
# self.assertEquals(ict.purchase_value, 1500)
|
||||
# self.assertEquals(ict.salvage_value, 0)
|
||||
self.assertEquals(ict.asset_value, 1500)
|
||||
vehicle = self.browse_ref('account_asset_management.'
|
||||
'account_asset_view_vehicle')
|
||||
# self.assertEquals(vehicle.purchase_value, 12000)
|
||||
# self.assertEquals(vehicle.salvage_value, 2000)
|
||||
self.assertEquals(vehicle.asset_value, 10000)
|
||||
fa = self.browse_ref('account_asset_management.'
|
||||
'account_asset_view_fa')
|
||||
# self.assertEquals(fa.purchase_value, 13500)
|
||||
# self.assertEquals(fa.salvage_value, 2000)
|
||||
self.assertEquals(fa.asset_value, 11500)
|
||||
|
||||
#
|
||||
# compute depreciation boards
|
||||
#
|
||||
self.asset_model.compute_depreciation_board(
|
||||
self.cr, self.uid, [ict0.id, vehicle0.id])
|
||||
ict0.refresh()
|
||||
self.assertEquals(len(ict0.depreciation_line_ids), 4)
|
||||
self.assertEquals(ict0.depreciation_line_ids[1].amount, 500)
|
||||
vehicle0.refresh()
|
||||
self.assertEquals(len(vehicle0.depreciation_line_ids), 6)
|
||||
self.assertEquals(vehicle0.depreciation_line_ids[1].amount, 2000)
|
||||
|
||||
#
|
||||
# post first depreciation line
|
||||
#
|
||||
ict0.validate()
|
||||
ict0.depreciation_line_ids[1].create_move()
|
||||
ict0.refresh()
|
||||
self.assertEquals(ict0.state, 'open')
|
||||
self.assertEquals(ict0.value_depreciated, 500)
|
||||
self.assertEquals(ict0.value_residual, 1000)
|
||||
ict.refresh()
|
||||
self.assertEquals(ict.value_depreciated, 500)
|
||||
self.assertEquals(ict.value_residual, 1000)
|
||||
vehicle0.validate()
|
||||
vehicle0.depreciation_line_ids[1].create_move()
|
||||
vehicle0.refresh()
|
||||
self.assertEquals(vehicle0.state, 'open')
|
||||
self.assertEquals(vehicle0.value_depreciated, 2000)
|
||||
self.assertEquals(vehicle0.value_residual, 8000)
|
||||
vehicle.refresh()
|
||||
self.assertEquals(vehicle.value_depreciated, 2000)
|
||||
self.assertEquals(vehicle.value_residual, 8000)
|
||||
fa.refresh()
|
||||
self.assertEquals(fa.value_depreciated, 2500)
|
||||
self.assertEquals(fa.value_residual, 9000)
|
||||
|
||||
#
|
||||
# change parent and check values
|
||||
#
|
||||
ict0.write({'parent_id': vehicle.id})
|
||||
ict.refresh()
|
||||
vehicle.refresh()
|
||||
fa.refresh()
|
||||
self.assertEquals(ict.value_depreciated, 0)
|
||||
self.assertEquals(ict.value_residual, 0)
|
||||
self.assertEquals(vehicle.value_depreciated, 2500)
|
||||
self.assertEquals(vehicle.value_residual, 9000)
|
||||
self.assertEquals(fa.value_depreciated, 2500)
|
||||
self.assertEquals(fa.value_residual, 9000)
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
from openerp.osv import fields, orm
|
||||
from openerp.tools.translate import _
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from datetime import datetime
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,25 +33,263 @@ class account_asset_remove(orm.TransientModel):
|
||||
_name = 'account.asset.remove'
|
||||
_description = 'Remove Asset'
|
||||
|
||||
_residual_value_regime_countries = ['FR']
|
||||
|
||||
def _posting_regime(self, cr, uid, context=None):
|
||||
return[
|
||||
('residual_value', _('Residual Value')),
|
||||
('gain_loss_on_sale', _('Gain/Loss on Sale')),
|
||||
]
|
||||
|
||||
def _get_posting_regime(self, cr, uid, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
asset_obj = self.pool.get('account.asset.asset')
|
||||
asset = asset_obj.browse(cr, uid, context.get('active_id'))
|
||||
country = asset and asset.company_id.country_id.code or False
|
||||
if country in self._residual_value_regime_countries:
|
||||
return 'residual_value'
|
||||
else:
|
||||
return 'gain_loss_on_sale'
|
||||
|
||||
def _get_sale(self, cr, uid, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
inv_line_obj = self.pool.get('account.invoice.line')
|
||||
currency_obj = self.pool.get('res.currency')
|
||||
asset_id = context.get('active_id')
|
||||
sale_value = 0.0
|
||||
account_sale_id = False
|
||||
inv_line_ids = inv_line_obj.search(
|
||||
cr, uid, [('asset_id', '=', asset_id)], context=context)
|
||||
for line in inv_line_obj.browse(cr, uid, inv_line_ids):
|
||||
inv = line.invoice_id
|
||||
comp_curr = inv.company_id.currency_id
|
||||
inv_curr = inv.currency_id
|
||||
if line.invoice_id.state in ['open', 'paid']:
|
||||
account_sale_id = line.account_id.id
|
||||
amount = line.price_subtotal
|
||||
if inv_curr != comp_curr:
|
||||
amount = currency_obj.compute(
|
||||
cr, uid, inv_curr.id, comp_curr.id, amount,
|
||||
context=context)
|
||||
sale_value += amount
|
||||
return {'sale_value': sale_value, 'account_sale_id': account_sale_id}
|
||||
|
||||
def _get_sale_value(self, cr, uid, context=None):
|
||||
return self._get_sale(cr, uid, context=context)['sale_value']
|
||||
|
||||
def _get_sale_account(self, cr, uid, context=None):
|
||||
return self._get_sale(cr, uid, context=context)['account_sale_id']
|
||||
|
||||
def _get_plus_account(self, cr, uid, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
acc = False
|
||||
asset_obj = self.pool.get('account.asset.asset')
|
||||
asset = asset_obj.browse(cr, uid, context.get('active_id'))
|
||||
if asset:
|
||||
acc = asset.category_id.account_plus_value_id
|
||||
return acc and acc.id or False
|
||||
|
||||
def _get_min_account(self, cr, uid, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
acc = False
|
||||
asset_obj = self.pool.get('account.asset.asset')
|
||||
asset = asset_obj.browse(cr, uid, context.get('active_id'))
|
||||
if asset:
|
||||
acc = asset.category_id.account_min_value_id
|
||||
return acc and acc.id or False
|
||||
|
||||
def _get_residual_account(self, cr, uid, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
acc = False
|
||||
asset_obj = self.pool.get('account.asset.asset')
|
||||
asset = asset_obj.browse(cr, uid, context.get('active_id'))
|
||||
if asset:
|
||||
acc = asset.category_id.account_residual_value_id
|
||||
return acc and acc.id or False
|
||||
|
||||
_columns = {
|
||||
'date_remove': fields.date('Asset Removal Date', required=True),
|
||||
'date_remove': fields.date(
|
||||
'Asset Removal Date', required=True,
|
||||
help="Removal date must be after the last posted entry "
|
||||
"in case of early removal"),
|
||||
'period_id': fields.many2one(
|
||||
'account.period', 'Force Period',
|
||||
domain=[('state', '<>', 'done')],
|
||||
help="Keep empty to use the period of the removal ate."),
|
||||
'sale_value': fields.float('Sale Value'),
|
||||
'account_sale_id': fields.many2one(
|
||||
'account.account', 'Asset Sale Account',
|
||||
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')]),
|
||||
'posting_regime': fields.selection(
|
||||
_posting_regime, 'Removal Entry Policy',
|
||||
required=True,
|
||||
help="Removal Entry Policy \n"
|
||||
" * Residual Value: The non-depreciated value will be "
|
||||
"posted on the 'Residual Value Account' \n"
|
||||
" * Gain/Loss on Sale: The Gain or Loss will be posted on "
|
||||
"the 'Plus-Value Account' or 'Min-Value Account' "),
|
||||
'note': fields.text('Notes'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'sale_value': _get_sale_value,
|
||||
'account_sale_id': _get_sale_account,
|
||||
'account_plus_value_id': _get_plus_account,
|
||||
'account_min_value_id': _get_min_account,
|
||||
'account_residual_value_id': _get_residual_account,
|
||||
'posting_regime': _get_posting_regime,
|
||||
}
|
||||
|
||||
_sql_constraints = [(
|
||||
'sale_value', 'CHECK (sale_value>=0)',
|
||||
'The Sale Value must be positive!')]
|
||||
|
||||
def _prepare_early_removal(self, cr, uid,
|
||||
asset, date_remove, context=None):
|
||||
"""
|
||||
Generate last depreciation entry on the day before the removal date.
|
||||
"""
|
||||
asset_line_obj = self.pool.get('account.asset.depreciation.line')
|
||||
|
||||
digits = self.pool.get('decimal.precision').precision_get(
|
||||
cr, uid, 'Account')
|
||||
|
||||
dl_ids = asset_line_obj.search(
|
||||
cr, uid,
|
||||
[('asset_id', '=', asset.id), ('type', '=', 'depreciate'),
|
||||
('init_entry', '=', False), ('move_check', '=', False)],
|
||||
order='line_date asc')
|
||||
first_to_depreciate_dl = asset_line_obj.browse(cr, uid, dl_ids[0])
|
||||
|
||||
first_date = first_to_depreciate_dl.line_date
|
||||
if date_remove > first_date:
|
||||
raise orm.except_orm(
|
||||
_('Error!'),
|
||||
_("You can't make an early removal if all the depreciation "
|
||||
"lines for previous periods are not posted."))
|
||||
|
||||
last_depr_date = first_to_depreciate_dl.previous_id.line_date
|
||||
period_number_days = (
|
||||
datetime.strptime(first_date, '%Y-%m-%d') -
|
||||
datetime.strptime(last_depr_date, '%Y-%m-%d')).days
|
||||
date_remove = datetime.strptime(date_remove, '%Y-%m-%d')
|
||||
new_line_date = date_remove + relativedelta(days=-1)
|
||||
to_depreciate_days = (
|
||||
new_line_date -
|
||||
datetime.strptime(last_depr_date, '%Y-%m-%d')).days
|
||||
to_depreciate_amount = round(
|
||||
float(to_depreciate_days) / float(period_number_days) *
|
||||
first_to_depreciate_dl.amount, digits)
|
||||
residual_value = asset.value_residual - to_depreciate_amount
|
||||
if to_depreciate_amount:
|
||||
update_vals = {
|
||||
'amount': to_depreciate_amount,
|
||||
'line_date': new_line_date
|
||||
}
|
||||
first_to_depreciate_dl.write(update_vals)
|
||||
asset_line_obj.create_move(
|
||||
cr, uid, [dl_ids[0]], context=context)
|
||||
dl_ids.pop(0)
|
||||
asset_line_obj.unlink(cr, uid, dl_ids, context=context)
|
||||
return residual_value
|
||||
|
||||
def _get_removal_data(self, cr, uid, wiz_data, asset, residual_value,
|
||||
context=None):
|
||||
move_lines = []
|
||||
partner_id = asset.partner_id and asset.partner_id.id or False
|
||||
categ = asset.category_id
|
||||
|
||||
# asset and asset depreciation account reversal
|
||||
depr_amount = asset.asset_value - residual_value
|
||||
move_line_vals = {
|
||||
'name': asset.name,
|
||||
'account_id': categ.account_depreciation_id.id,
|
||||
'debit': depr_amount > 0 and depr_amount or 0.0,
|
||||
'credit': depr_amount < 0 and -depr_amount or 0.0,
|
||||
'partner_id': partner_id,
|
||||
'asset_id': asset.id
|
||||
}
|
||||
move_lines.append((0, 0, move_line_vals))
|
||||
move_line_vals = {
|
||||
'name': asset.name,
|
||||
'account_id': categ.account_asset_id.id,
|
||||
'debit': asset.asset_value < 0 and -asset.asset_value or 0.0,
|
||||
'credit': asset.asset_value > 0 and asset.asset_value or 0.0,
|
||||
'partner_id': partner_id,
|
||||
'asset_id': asset.id
|
||||
}
|
||||
move_lines.append((0, 0, move_line_vals))
|
||||
|
||||
if residual_value:
|
||||
if wiz_data.posting_regime == 'residual_value':
|
||||
move_line_vals = {
|
||||
'name': asset.name,
|
||||
'account_id': wiz_data.account_residual_value_id.id,
|
||||
'debit': residual_value,
|
||||
'credit': 0.0,
|
||||
'partner_id': partner_id,
|
||||
'asset_id': asset.id
|
||||
}
|
||||
move_lines.append((0, 0, move_line_vals))
|
||||
elif wiz_data.posting_regime == 'gain_loss_on_sale':
|
||||
if wiz_data.sale_value:
|
||||
sale_value = wiz_data.sale_value
|
||||
move_line_vals = {
|
||||
'name': asset.name,
|
||||
'account_id': wiz_data.account_sale_id.id,
|
||||
'debit': sale_value,
|
||||
'credit': 0.0,
|
||||
'partner_id': partner_id,
|
||||
'asset_id': asset.id
|
||||
}
|
||||
move_lines.append((0, 0, move_line_vals))
|
||||
balance = wiz_data.sale_value - residual_value
|
||||
account_id = balance > 0 and wiz_data.account_plus_value_id.id \
|
||||
or wiz_data.account_min_value_id.id
|
||||
move_line_vals = {
|
||||
'name': asset.name,
|
||||
'account_id': account_id,
|
||||
'debit': balance < 0 and -balance or 0.0,
|
||||
'credit': balance > 0 and balance or 0.0,
|
||||
'partner_id': partner_id,
|
||||
'asset_id': asset.id
|
||||
}
|
||||
move_lines.append((0, 0, move_line_vals))
|
||||
|
||||
return move_lines
|
||||
|
||||
def remove(self, cr, uid, ids, context=None):
|
||||
asset_obj = self.pool.get('account.asset.asset')
|
||||
asset_line_obj = self.pool.get('account.asset.depreciation.line')
|
||||
move_line_obj = self.pool.get('account.move.line')
|
||||
move_obj = self.pool.get('account.move')
|
||||
period_obj = self.pool.get('account.period')
|
||||
|
||||
wiz_data = self.browse(cr, uid, ids[0], context=context)
|
||||
asset_id = context['active_id']
|
||||
asset = asset_obj.browse(cr, uid, asset_id, context=context)
|
||||
asset_ref = asset.code and '%s (ref: %s)' \
|
||||
% (asset.name, asset.code) or asset.name
|
||||
wiz_data = self.browse(cr, uid, ids[0], context=context)
|
||||
|
||||
if context.get('early_removal'):
|
||||
residual_value = self._prepare_early_removal(
|
||||
cr, uid, asset, wiz_data.date_remove, context=context)
|
||||
else:
|
||||
residual_value = asset.residual_value
|
||||
|
||||
ctx = dict(context, company_id=asset.company_id.id)
|
||||
period_id = wiz_data.period_id and wiz_data.period_id.id or False
|
||||
if not period_id:
|
||||
@@ -82,33 +322,6 @@ class account_asset_remove(orm.TransientModel):
|
||||
'narration': wiz_data.note,
|
||||
}
|
||||
move_id = move_obj.create(cr, uid, move_vals, context=context)
|
||||
partner_id = asset.partner_id and asset.partner_id.id or False
|
||||
move_line_obj.create(cr, uid, {
|
||||
'name': asset.name,
|
||||
'ref': line_name,
|
||||
'move_id': move_id,
|
||||
'account_id': asset.category_id.account_depreciation_id.id,
|
||||
'debit': asset.asset_value > 0 and asset.asset_value or 0.0,
|
||||
'credit': asset.asset_value < 0 and -asset.asset_value or 0.0,
|
||||
'period_id': period_id,
|
||||
'journal_id': journal_id,
|
||||
'partner_id': partner_id,
|
||||
'date': wiz_data.date_remove,
|
||||
'asset_id': asset.id
|
||||
}, context={'allow_asset': True})
|
||||
move_line_obj.create(cr, uid, {
|
||||
'name': asset.name,
|
||||
'ref': line_name,
|
||||
'move_id': move_id,
|
||||
'account_id': asset.category_id.account_asset_id.id,
|
||||
'debit': asset.asset_value < 0 and -asset.asset_value or 0.0,
|
||||
'credit': asset.asset_value > 0 and asset.asset_value or 0.0,
|
||||
'period_id': period_id,
|
||||
'journal_id': journal_id,
|
||||
'partner_id': partner_id,
|
||||
'date': wiz_data.date_remove,
|
||||
'asset_id': asset.id
|
||||
}, context={'allow_asset': True})
|
||||
|
||||
# create asset line
|
||||
asset_line_vals = {
|
||||
@@ -122,6 +335,20 @@ class account_asset_remove(orm.TransientModel):
|
||||
asset_line_obj.create(cr, uid, asset_line_vals, context=context)
|
||||
asset.write({'state': 'removed', 'date_remove': wiz_data.date_remove})
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
# create move lines
|
||||
move_lines = self._get_removal_data(
|
||||
cr, uid, wiz_data, asset, residual_value, context=context)
|
||||
move_obj.write(cr, uid, [move_id], {'line_id': move_lines},
|
||||
context=dict(context, allow_asset=True))
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
return {
|
||||
'name': _("Asset '%s' Removal Journal Entry") % asset_ref,
|
||||
'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', '=', move_id)],
|
||||
}
|
||||
|
||||
@@ -7,12 +7,19 @@
|
||||
<field name="model">account.asset.remove</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Remove Asset" version="7.0">
|
||||
<group colspan="4" col="2">
|
||||
<separator string="Specify the asset removal date" colspan="2"/>
|
||||
<group colspan="4" col="4">
|
||||
<field name="date_remove"/>
|
||||
<field name="period_id"/>
|
||||
<separator string="Notes" colspan="2"/>
|
||||
<field name="note" nolabel="1" colspan="2"/>
|
||||
<field name="sale_value"/>
|
||||
<field name="account_sale_id" attrs="{'invisible': [('sale_value', '=', 0.0)], 'required': [('sale_value', '>', 0.0)]}"/>
|
||||
<newline/>
|
||||
<field name="posting_regime"/>
|
||||
<newline/>
|
||||
<field name="account_plus_value_id" attrs="{'invisible': [('posting_regime', '=', 'residual_value')], 'required': [('posting_regime', '!=', 'residual_value')]}"/>
|
||||
<field name="account_min_value_id" attrs="{'invisible': [('posting_regime', '=', 'residual_value')], 'required': [('posting_regime', '!=', 'residual_value')]}"/>
|
||||
<field name="account_residual_value_id" attrs="{'invisible': [('posting_regime', '!=', 'residual_value')], 'required': [('posting_regime', '=', 'residual_value')]}"/>
|
||||
<separator string="Notes" colspan="4"/>
|
||||
<field name="note" nolabel="1" colspan="4"/>
|
||||
</group>
|
||||
<newline/>
|
||||
<separator colspan="4"/>
|
||||
|
||||
Reference in New Issue
Block a user