[MIG] account_asset_disposal: Migrated to 10.0

This commit is contained in:
Luis M. Ontalba
2017-07-13 20:36:16 +02:00
committed by OCA-git-bot
parent afdf215b2b
commit ee8fe371c2
14 changed files with 154 additions and 391 deletions

View File

@@ -1,28 +1,24 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
:target: http://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
======================
Account asset disposal
======================
This module extends the functionality of account_asset and automatically
generates the asset close move
This module extends the functionality of account_asset adding a disposal
date, allowing reversion of disposal operation and adding the state
"Disposed" to the asset.
Usage
=====
When an asset is broken or is totally depreciated you can close it and Odoo
will generate automacally the asset close move (and compute the loss if a residual
value is pending)
In asset you will find a 'Disposal' button (instead of standard 'Set to Close').
After click, a wizard pops-up for asking disposal date and loss account to use
if any residual value is pending.
In a disposed asset you will find an 'Undo disposal' button to revert
operation.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/92/8.0
:target: https://runbot.odoo-community.org/runbot/92/10.0
Bug Tracker
@@ -46,6 +42,7 @@ Contributors
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
* Antonio Espinosa <antonio.espinosa@tecnativa.com>
* Luis M. Ontalba <luis.martinez@tecnativa.com>
Maintainer
----------

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
from . import wizards

View File

@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# Copyright 2017 Luis M. Ontalba - <luis.martinez@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Account asset disposal",
"summary": "Makes asset close account move automatically",
"version": "8.0.1.0.0",
"version": "10.0.1.0.0",
"category": "Accounting & Finance",
"website": "http://www.tecnativa.com",
"website": "https://www.tecnativa.com",
"author": "Tecnativa, "
"Odoo Community Association (OCA)",
"license": "AGPL-3",
@@ -15,9 +16,9 @@
"installable": True,
"depends": [
"account_asset",
"account_cancel",
],
"data": [
"views/account_asset_asset_view.xml",
"wizards/account_asset_disposal_wizard_view.xml",
],
}

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright Stephane LE CORNEC
# Copyright 2017 Tecnativa - Pedro M. Baeza <pedro.baeza@tecnativa.com>
# Copyright 2017 Tecnativa - Luis M. Ontalba <luis.martinez@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openupgradelib import openupgrade
def cleanup_modules(cr):
"""Don't report as missing these modules, as they are integrated in
other modules."""
openupgrade.update_module_names(
cr, [
('account_asset_disposal_analytic', 'account_asset_analytic'),
], merge_modules=True,
)
@openupgrade.migrate()
def migrate(cr, version):
cleanup_modules(cr)

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import account_asset_category
from . import account_asset_asset
from . import account_move_line

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# Copyright 2017 Luis M. Ontalba - <luis.martinez@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, _
from odoo import api, fields, models
class AccountAssetAsset(models.Model):
@@ -15,91 +16,20 @@ class AccountAssetAsset(models.Model):
disposal_move_id = fields.Many2one(
comodel_name='account.move', string="Disposal move")
def _disposal_line_asset_prepare(self, date, period, journal):
return {
'name': _('Asset disposal'),
'journal_id': journal.id,
'period_id': period.id,
'account_id': self.category_id.account_asset_id.id,
'asset_id': self.id,
'date': date,
'debit': 0.0,
'credit': self.purchase_value,
}
def _disposal_line_depreciation_prepare(self, date, period, journal,
depreciation_value):
return {
'name': _('Asset depreciation'),
'journal_id': journal.id,
'period_id': period.id,
'account_id': self.category_id.account_depreciation_id.id,
'asset_id': self.id,
'date': date,
'debit': depreciation_value,
'credit': 0.0,
}
def _disposal_line_loss_prepare(self, date, period, journal, loss_account,
loss_value):
return {
'name': _('Asset loss'),
'journal_id': journal.id,
'period_id': period.id,
'account_id': loss_account.id,
'analytic_account_id': self.category_id.account_analytic_id.id,
'asset_id': self.id,
'date': date,
'debit': loss_value,
'credit': 0.0,
}
def _disposal_move_prepare(self, date, loss_account):
journal = self.category_id.journal_id
period = self.env['account.period'].find(date)
loss_value = self.salvage_value + self.value_residual
depreciation_value = self.purchase_value - loss_value
line_asset = self._disposal_line_asset_prepare(date, period, journal)
line_depreciation = self._disposal_line_depreciation_prepare(
date, period, journal, depreciation_value)
lines = [
(0, False, line_asset),
(0, False, line_depreciation),
]
if loss_value:
line_loss = self._disposal_line_loss_prepare(
date, period, journal, loss_account, loss_value)
lines.append((0, False, line_loss))
return {
'journal_id': journal.id,
'period_id': period.id,
'ref': self.name,
'date': date,
'line_id': lines,
}
def get_disposal_date(self):
return fields.Date.context_today(self)
@api.multi
def disposal_move_create(self, date, loss_account):
for asset in self:
vals = self._disposal_move_prepare(date, loss_account)
asset.disposal_move_id = self.env['account.move'].create(vals)
if asset.disposal_move_id:
asset.disposal_move_id.post()
@api.multi
def action_disposal(self):
wizard_view_id = self.env.ref(
'account_asset_disposal.account_asset_disposal_wizard_form')
return {
'name': 'Disposal asset',
'res_model': 'account.asset.disposal.wizard',
'type': 'ir.actions.act_window',
'view_type': 'tree,form',
'view_mode': 'form',
'view_id': wizard_view_id.id,
'target': 'new',
'context': self.env.context,
}
def set_to_close(self):
res = super(AccountAssetAsset, self).set_to_close()
if res:
self.disposal_move_id = res['res_id']
self.disposal_move_id.post()
self.write({
'state': 'disposed',
'disposal_date': self.get_disposal_date(),
})
return res
@api.multi
def action_disposal_undo(self):
@@ -111,7 +41,21 @@ class AccountAssetAsset(models.Model):
asset.state = 'close'
else:
asset.state = 'open'
asset.method_end = asset.category_id.method_end
asset.method_number = asset.category_id.method_number
asset.compute_depreciation_board()
return self.write({
'disposal_date': False,
'disposal_move_id': False,
})
class AccountAssetDepreciationLine(models.Model):
_inherit = 'account.asset.depreciation.line'
@api.multi
def post_lines_and_close_asset(self):
disposed_lines = self.filtered(lambda r: r.asset_id.state ==
'disposed')
super(AccountAssetDepreciationLine, self).post_lines_and_close_asset()
disposed_lines.mapped('asset_id').write({'state': 'disposed'})

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields
class AccountAssetCategory(models.Model):
_inherit = "account.asset.category"
def _default_loss_account_id(self):
exp_type = self.env.ref('account.data_account_type_expense')
first_expense = self.env['account.account'].search([
('type', '=', 'other'),
('user_type', '=', exp_type.id),
], limit=1)
return first_expense
loss_account_id = fields.Many2one(
comodel_name="account.account", string="Loss Account", required=True,
domain=[('type', '=', 'other')],
default=lambda self: self._default_loss_account_id())

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# Copyright 2017 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, api, _
from openerp.exceptions import Warning as UserError
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
@api.multi
def unlink(self, check=True):
for line in self:
if (line.asset_id.state == 'disposed' and
not self.env.context.get('asset_disposal_undo', False)):
name = '%s:%s' % (line.move_id.name, line.name)
raise UserError(
_("Move line '%s' is related with a disposed asset '%s'") %
(name, line.asset_id.name))
return super(AccountMoveLine, self).unlink(check=check)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_account_asset_disposal

View File

@@ -1,128 +1,72 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# Copyright 2017 Tecnativa - Luis M. Ontalba - <luis.martinez@tecnativa.com>
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0
from openerp.tests.common import TransactionCase
from odoo.tests import common
from odoo import fields
from datetime import datetime
class TestAccountAsset(TransactionCase):
def setUp(self):
super(TestAccountAsset, self).setUp()
# Create a payable account for suppliers
self.account_suppliers = self.env['account.account'].create({
'name': 'Suppliers',
'code': '410x',
'type': 'other',
'user_type': self.env.ref('account.data_account_type_payable').id,
'reconcile': True,
})
# Create a supplier
self.supplier = self.env['res.partner'].create({
'name': 'Asset provider',
'supplier': True,
'customer': False,
})
# Create a journal for purchases
self.journal_purchase = self.env['account.journal'].create({
'name': 'Purchase journal',
'code': 'PRCH',
'type': 'purchase',
})
# Create a journal for assets
self.journal_asset = self.env['account.journal'].create({
'name': 'Asset journal',
'code': 'JRNL',
class TestAccountAssetDisposal(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestAccountAssetDisposal, cls).setUpClass()
cls.journal = cls.env['account.journal'].create({
'name': 'Test Journal',
'type': 'general',
'code': 'TJ',
'update_posted': True,
})
# Create an account for assets
self.account_asset = self.env['account.account'].create({
'name': 'Asset',
'code': '216x',
'type': 'other',
'user_type': self.env.ref('account.data_account_type_asset').id,
'reconcile': False,
cls.account_type = cls.env['account.account.type'].create({
'name': 'Test Account Type',
})
# Create an account for assets dereciation
self.account_asset_depreciation = self.env['account.account'].create({
'name': 'Asset depreciation',
'code': '2816x',
'type': 'other',
'user_type': self.env.ref('account.data_account_type_asset').id,
'reconcile': False,
cls.asset_account = cls.env['account.account'].create({
'code': 'TAA',
'name': 'Test Asset Account',
'internal_type': 'other',
'user_type_id': cls.account_type.id,
})
# Create an account for assets expense
self.account_asset_expense = self.env['account.account'].create({
'name': 'Asset expense',
'code': '681x',
'type': 'other',
'user_type': self.env.ref('account.data_account_type_expense').id,
'reconcile': False,
cls.asset_category_number = cls.env['account.asset.category'].create({
'name': 'Test Category Number',
'journal_id': cls.journal.id,
'account_asset_id': cls.asset_account.id,
'account_depreciation_id': cls.asset_account.id,
'account_depreciation_expense_id': cls.asset_account.id,
'method_time': 'number',
'method_number': 10,
'method_period': 12,
'method': 'linear',
})
# Create an account for assets loss
self.account_asset_loss = self.env['account.account'].create({
'name': 'Asset loss',
'code': '671x',
'type': 'other',
'user_type': self.env.ref('account.data_account_type_expense').id,
'reconcile': False,
cls.asset = cls.env['account.asset.asset'].create({
'name': 'Test Asset Number',
'category_id': cls.asset_category_number.id,
'value': 16000.0,
'salvage_value': 1000.0,
'method_number': 15,
})
# Create an assset category, with analytic account A
self.asset_category = self.env['account.asset.category'].create({
'name': 'Asset category for testing',
'journal_id': self.journal_asset.id,
'account_asset_id': self.account_asset.id,
'account_depreciation_id': self.account_asset_depreciation.id,
'account_expense_depreciation_id': self.account_asset_expense.id,
})
# Create an invoice
self.asset_name = 'Office table'
self.invoice = self.env['account.invoice'].create({
'partner_id': self.supplier.id,
'account_id': self.account_suppliers.id,
'journal_id': self.journal_purchase.id,
'reference_type': 'none',
'reference': 'PURCHASE/12345',
'invoice_line': [
(0, False, {
'name': self.asset_name,
'account_id': self.account_asset.id,
'asset_category_id': self.asset_category.id,
'quantity': 1.0,
'price_unit': 100.00,
}),
],
})
# Validate invoice
self.invoice.signal_workflow('invoice_open')
# Last period opened
self.last_period = self.env['account.period'].search([
('state', '=', 'draft'),
('special', '=', False),
], limit=1, order='date_stop DESC')
cls.asset.validate()
cls.date_time = fields.Date.to_string(datetime.now())
def test_asset_disposal(self):
# Search asset created
asset = self.env['account.asset.asset'].search([
('code', '=', self.invoice.number),
])
# Asset must be created with code == invoice number
self.assertTrue(asset)
# Depreciate the first line
line = asset.depreciation_line_ids.filtered(
lambda x: x.move_check is False)[0]
line.create_move()
# Disposal asset
disposal_date = self.last_period.date_stop
wizard = self.env['account.asset.disposal.wizard'].with_context(
active_ids=[asset.id]).create({
'disposal_date': disposal_date,
'loss_account_id': self.account_asset_loss.id,
})
wizard.action_disposal()
# Disposal date
self.assertEqual(disposal_date, asset.disposal_date)
# Disposal move exists and posted
self.assertTrue(asset.disposal_move_id)
self.assertEqual('posted', asset.disposal_move_id.state)
# Disposal move amount must be equal to asset purchase value
self.assertEqual(asset.purchase_value, asset.disposal_move_id.amount)
def test_asset_depreciation_board(self):
self.assertEqual(len(self.asset.depreciation_line_ids), 15)
self.first_line = self.asset.depreciation_line_ids[0]
self.assertEqual(self.first_line.depreciated_value, 1000.0)
self.assertEqual(self.first_line.remaining_value, 14000.0)
def test_asset_unamortized(self):
self.asset.set_to_close()
self.assertTrue(self.asset.disposal_move_id)
self.assertEqual(self.asset.disposal_date, self.date_time)
self.assertEqual(self.asset.state, 'disposed')
self.asset.action_disposal_undo()
self.assertEqual(self.asset.state, 'open')
self.assertEqual(self.asset.method_end,
self.asset.category_id.method_end)
self.assertEqual(self.asset.method_number,
self.asset.category_id.method_number)
def test_asset_amortized(self):
self.asset.depreciation_line_ids.create_move()
self.asset.set_to_close()
self.asset.action_disposal_undo()
self.assertEqual(self.asset.state, 'close')

View File

@@ -1,54 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
Copyright 2017 Pedro M. Baeza <pedro.baeza@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<openerp>
<data>
<record model="ir.ui.view" id="view_account_asset_asset_form">
<field name="name">Add disposal fields</field>
<field name="model">account.asset.asset</field>
<field name="inherit_id" ref="account_asset.view_account_asset_asset_form"/>
<field name="arch" type="xml">
<button name="set_to_close" position="attributes">
<attribute name='invisible'>1</attribute>
</button>
<button name="compute_depreciation_board" position="attributes">
<attribute name="attrs">{'invisible':[('state', '!=', 'draft')]}</attribute>
</button>
<button name="set_to_close" position="after">
<button name="action_disposal"
states="open,close"
string="Disposal"
type="object"
class="oe_highlight"
/>
<button name="action_disposal_undo"
states="disposed"
string="Undo disposal"
type="object"
class="oe_highlight"
/>
</button>
<field name="purchase_date" position="after">
<field name="disposal_date"
attrs="{'invisible': [('state', '!=', 'disposed')]}"/>
<field name="disposal_move_id"
attrs="{'invisible': [('state', '!=', 'disposed')]}"/>
<odoo>
<record model="ir.ui.view" id="view_account_asset_asset_form">
<field name="name">Add disposal fields</field>
<field name="model">account.asset.asset</field>
<field name="inherit_id"
ref="account_asset.view_account_asset_asset_form"/>
<field name="arch" type="xml">
<button name="compute_depreciation_board" position="attributes">
<attribute name="attrs">{
'invisible':[('state', '!=', 'draft')]}</attribute>
</button>
<button name="set_to_close" position="after">
<button name="action_disposal_undo"
states="disposed"
string="Undo disposal"
type="object"
class="oe_highlight"
/>
</button>
<field name="date" position="after">
<field name="disposal_date"
attrs="{'invisible': [('state', '!=', 'disposed')]}"/>
<field name="disposal_move_id"
attrs="{'invisible': [('state', '!=', 'disposed')]}"/>
</field>
</field>
</field>
</record>
</record>
<record model="ir.ui.view" id="view_account_asset_category_form">
<field name="name">Add loss account</field>
<field name="model">account.asset.category</field>
<field name="inherit_id" ref="account_asset.view_account_asset_category_form"/>
<field name="arch" type="xml">
<field name="account_expense_depreciation_id" position="after">
<field name="loss_account_id"/>
</field>
</field>
</record>
</data>
</openerp>
</odoo>

View File

@@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import account_asset_disposal_wizard

View File

@@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api
class AccountAssetDisposalWizard(models.TransientModel):
_name = 'account.asset.disposal.wizard'
def _default_disposal_date(self):
return fields.Date.context_today(self)
def _default_loss_account_id(self):
return self.env['account.asset.category']._default_loss_account_id()
disposal_date = fields.Date(
string="Disposal date", require=True,
default=lambda self: self._default_disposal_date())
loss_account_id = fields.Many2one(
comodel_name='account.account', string="Loss account", require=True,
domain=[('type', '=', 'other')],
default=lambda self: self._default_loss_account_id())
def _disposal_date_set(self, assets):
assets.write({'disposal_date': self.disposal_date})
@api.multi
def action_disposal(self):
self.ensure_one()
assets = self.env['account.asset.asset'].browse(
self.env.context.get('active_ids', False))
for asset in assets:
asset.disposal_move_create(
self.disposal_date, self.loss_account_id)
self._disposal_date_set(assets)
return assets.write({'state': 'disposed'})

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<openerp>
<data>
<record model="ir.ui.view" id="account_asset_disposal_wizard_form">
<field name="name">account.asset.disposal.wizard.form</field>
<field name="model">account.asset.disposal.wizard</field>
<field name="arch" type="xml">
<form string="Asset disposal">
<group>
<group>
<field name="disposal_date"/>
</group>
<group>
<field name="loss_account_id"/>
</group>
</group>
<footer>
<button name="action_disposal"
string="Disposal asset"
type="object" class="oe_highlight" />
or
<button special="cancel" string="Close" class="oe_link" />
</footer>
</form>
</field>
</record>
</data>
</openerp>