2
0

[IMP] account_asset_management: black, isort

This commit is contained in:
ernestotejeda 2020-01-29 12:34:31 -05:00 committed by Rodrigo
parent 7791c15d76
commit bc3e0f8fa6
19 changed files with 1826 additions and 1590 deletions

View File

@ -3,30 +3,28 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Assets Management', "name": "Assets Management",
'version': '12.0.2.1.3', "version": "12.0.2.1.3",
'license': 'AGPL-3', "license": "AGPL-3",
'depends': [ "depends": ["account"],
'account', "excludes": ["account_asset"],
], "author": "Noviat,Odoo Community Association (OCA)",
'excludes': ['account_asset'], "website": "https://github.com/OCA/account-financial-tools",
'author': "Noviat,Odoo Community Association (OCA)", "category": "Accounting & Finance",
'website': 'https://github.com/OCA/account-financial-tools', "data": [
'category': 'Accounting & Finance', "security/account_asset_security.xml",
'data': [ "security/ir.model.access.csv",
'security/account_asset_security.xml', "wizard/account_asset_compute.xml",
'security/ir.model.access.csv', "wizard/account_asset_remove.xml",
'wizard/account_asset_compute.xml', "views/account_account.xml",
'wizard/account_asset_remove.xml', "views/account_asset.xml",
'views/account_account.xml', "views/account_asset_group.xml",
'views/account_asset.xml', "views/account_asset_profile.xml",
'views/account_asset_group.xml', "views/res_config_settings.xml",
'views/account_asset_profile.xml', "views/account_invoice.xml",
'views/res_config_settings.xml', "views/account_invoice_line.xml",
'views/account_invoice.xml', "views/account_move.xml",
'views/account_invoice_line.xml', "views/account_move_line.xml",
'views/account_move.xml', "views/menuitem.xml",
'views/account_move_line.xml',
'views/menuitem.xml',
], ],
} }

View File

@ -154,4 +154,4 @@ DEL ir.ui.view: account_asset.view_account_asset_search
DEL ir.ui.view: account_asset.view_asset_asset_report_search DEL ir.ui.view: account_asset.view_asset_asset_report_search
DEL ir.ui.view: account_asset.view_asset_depreciation_confirmation_wizard DEL ir.ui.view: account_asset.view_asset_depreciation_confirmation_wizard
DEL ir.ui.view: account_asset.view_invoice_asset_category DEL ir.ui.view: account_asset.view_invoice_asset_category
DEL ir.ui.view: account_asset.view_product_template_form_inherit DEL ir.ui.view: account_asset.view_product_template_form_inherit

View File

@ -3,6 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openupgradelib import openupgrade from openupgradelib import openupgrade
from psycopg2 import sql from psycopg2 import sql
from odoo.tools import float_is_zero from odoo.tools import float_is_zero
@ -12,31 +13,33 @@ def adjust_asset_values(env):
""" """
# Copy analytic account value # Copy analytic account value
openupgrade.logged_query( openupgrade.logged_query(
env.cr, """ env.cr,
"""
UPDATE account_asset aa UPDATE account_asset aa
SET account_analytic_id = aap.account_analytic_id SET account_analytic_id = aap.account_analytic_id
FROm account_asset_profile aap FROm account_asset_profile aap
WHERE aa.profile_id = aap.id""", WHERE aa.profile_id = aap.id""",
) )
# Adjust method_time, method_number and method_period # Adjust method_time, method_number and method_period
number = sql.Identifier(openupgrade.get_legacy_name('method_number')) number = sql.Identifier(openupgrade.get_legacy_name("method_number"))
period = sql.Identifier(openupgrade.get_legacy_name('method_period')) period = sql.Identifier(openupgrade.get_legacy_name("method_period"))
for table in ['account_asset_profile', 'account_asset']: for table in ["account_asset_profile", "account_asset"]:
table = sql.Identifier(table) table = sql.Identifier(table)
openupgrade.logged_query( openupgrade.logged_query(
env.cr, sql.SQL(""" env.cr,
sql.SQL(
"""
UPDATE {table} UPDATE {table}
SET method_time = 'year', SET method_time = 'year',
method_number = ({number} * {period}) / 12 method_number = ({number} * {period}) / 12
WHERE MOD({number} * {period}, 12) = 0 WHERE MOD({number} * {period}, 12) = 0
""").format( """
number=number, ).format(number=number, period=period, table=table),
period=period,
table=table,
),
) )
openupgrade.logged_query( openupgrade.logged_query(
env.cr, sql.SQL(""" env.cr,
sql.SQL(
"""
UPDATE {table} UPDATE {table}
SET method_period = (CASE SET method_period = (CASE
WHEN {period} = 1 THEN 'month' WHEN {period} = 1 THEN 'month'
@ -44,16 +47,15 @@ def adjust_asset_values(env):
WHEN {period} = 12 THEN 'year' WHEN {period} = 12 THEN 'year'
END) END)
WHERE {period} IN (1, 3, 12) WHERE {period} IN (1, 3, 12)
""").format( """
period=period, ).format(period=period, table=table),
table=table,
),
) )
def adjust_aml_values(env): def adjust_aml_values(env):
openupgrade.logged_query( openupgrade.logged_query(
env.cr, """ env.cr,
"""
UPDATE account_move_line aml UPDATE account_move_line aml
SET asset_id = aa.id, SET asset_id = aa.id,
asset_profile_id = aa.profile_id asset_profile_id = aa.profile_id
@ -70,29 +72,31 @@ def handle_account_asset_disposal_migration(env):
In this phase we set the last asset line to the type remove on the In this phase we set the last asset line to the type remove on the
corresponding assets. corresponding assets.
""" """
column_name = openupgrade.get_legacy_name('disposal_move_id') column_name = openupgrade.get_legacy_name("disposal_move_id")
if not openupgrade.column_exists(env.cr, 'account_asset', column_name): if not openupgrade.column_exists(env.cr, "account_asset", column_name):
return return
env.cr.execute( env.cr.execute(
sql.SQL( sql.SQL("SELECT id FROM account_asset WHERE {col} IS NOT NULL").format(
"SELECT id FROM account_asset WHERE {col} IS NOT NULL" col=sql.Identifier(column_name)
).format(col=sql.Identifier(column_name)) )
) )
assets = env['account.asset'].with_context( assets = (
allow_asset_line_update=True, env["account.asset"]
).browse(x[0] for x in env.cr.fetchall()) .with_context(allow_asset_line_update=True)
assets.mapped('depreciation_line_ids').filtered( .browse(x[0] for x in env.cr.fetchall())
)
assets.mapped("depreciation_line_ids").filtered(
lambda x: float_is_zero( lambda x: float_is_zero(
x.remaining_value, x.remaining_value,
precision_rounding=x.asset_id.company_currency_id.rounding, precision_rounding=x.asset_id.company_currency_id.rounding,
) )
).write({'type': 'remove'}) ).write({"type": "remove"})
@openupgrade.migrate() @openupgrade.migrate()
def migrate(env, version): def migrate(env, version):
copied_column = openupgrade.get_legacy_name('method_time') copied_column = openupgrade.get_legacy_name("method_time")
if not openupgrade.column_exists(env.cr, 'account_asset', copied_column): if not openupgrade.column_exists(env.cr, "account_asset", copied_column):
# We avoid this migration if `account_asset` was not installed in v11 # We avoid this migration if `account_asset` was not installed in v11
return return
adjust_asset_values(env) adjust_asset_values(env)

View File

@ -4,47 +4,46 @@
from openupgradelib import openupgrade from openupgradelib import openupgrade
_model_renames = [ _model_renames = [
('account.asset.category', 'account.asset.profile'), ("account.asset.category", "account.asset.profile"),
('account.asset.depreciation.line', 'account.asset.line'), ("account.asset.depreciation.line", "account.asset.line"),
('account.asset.asset', 'account.asset'), ("account.asset.asset", "account.asset"),
] ]
_table_renames = [ _table_renames = [
(old.replace('.', '_'), new.replace('.', '_')) (old.replace(".", "_"), new.replace(".", "_")) for (old, new) in _model_renames
for (old, new) in _model_renames
] ]
_column_copies = { _column_copies = {
'account_asset': [ "account_asset": [("method_number", None, None), ("method_time", None, None)],
('method_number', None, None), "account_asset_profile": [
('method_time', None, None), ("method_number", None, None),
], ("method_time", None, None),
'account_asset_profile': [
('method_number', None, None),
('method_time', None, None),
], ],
} }
_column_renames = { _column_renames = {
'account_asset': [ "account_asset": [("method_period", None)],
('method_period', None), "account_asset_profile": [("method_period", None)],
],
'account_asset_profile': [
('method_period', None),
],
} }
_field_renames = [ _field_renames = [
('account.asset', 'account_asset', 'category_id', 'profile_id'), ("account.asset", "account_asset", "category_id", "profile_id"),
('account.asset', 'account_asset', 'currency_id', 'company_currency_id'), ("account.asset", "account_asset", "currency_id", "company_currency_id"),
('account.asset', 'account_asset', 'date', 'date_start'), ("account.asset", "account_asset", "date", "date_start"),
('account.asset', 'account_asset', 'value', 'purchase_value'), ("account.asset", "account_asset", "value", "purchase_value"),
('account.asset.line', 'account_asset_line', ("account.asset.line", "account_asset_line", "depreciation_date", "line_date"),
'depreciation_date', 'line_date'), (
('account.asset.profile', 'account_asset_profile', "account.asset.profile",
'account_depreciation_expense_id', 'account_expense_depreciation_id'), "account_asset_profile",
('account.invoice.line', 'account_invoice_line', "account_depreciation_expense_id",
'asset_category_id', 'asset_profile_id'), "account_expense_depreciation_id",
),
(
"account.invoice.line",
"account_invoice_line",
"asset_category_id",
"asset_profile_id",
),
] ]
@ -54,20 +53,27 @@ def handle_account_asset_disposal_migration(env):
In this phase we rename stuff for adapting to the new data structure. In this phase we rename stuff for adapting to the new data structure.
""" """
cr = env.cr cr = env.cr
if not openupgrade.column_exists(cr, 'account_asset', 'disposal_move_id'): if not openupgrade.column_exists(cr, "account_asset", "disposal_move_id"):
return return
openupgrade.copy_columns(cr, {'account_asset': [('state', None, None)]}) openupgrade.copy_columns(cr, {"account_asset": [("state", None, None)]})
openupgrade.rename_columns( openupgrade.rename_columns(cr, {"account_asset": [("disposal_move_id", None)]})
cr, {'account_asset': [('disposal_move_id', None)]})
openupgrade.map_values( openupgrade.map_values(
cr, openupgrade.get_legacy_name('state'), 'state', cr,
[('disposed', 'removed')], table='account_asset', openupgrade.get_legacy_name("state"),
"state",
[("disposed", "removed")],
table="account_asset",
) )
openupgrade.rename_fields( openupgrade.rename_fields(
env, [ env,
('account.asset', 'account_asset', 'disposal_date', 'date_remove'), [
('account.asset.profile', 'account_asset_profile', ("account.asset", "account_asset", "disposal_date", "date_remove"),
'account_loss_id', 'account_residual_value_id'), (
"account.asset.profile",
"account_asset_profile",
"account_loss_id",
"account_residual_value_id",
),
], ],
) )
@ -75,9 +81,9 @@ def handle_account_asset_disposal_migration(env):
@openupgrade.migrate() @openupgrade.migrate()
def migrate(env, version): def migrate(env, version):
cr = env.cr cr = env.cr
if openupgrade.table_exists(cr, 'account_asset_asset'): if openupgrade.table_exists(cr, "account_asset_asset"):
# `account_asset` module was installed in v11 # `account_asset` module was installed in v11
if openupgrade.table_exists(cr, 'account_asset'): if openupgrade.table_exists(cr, "account_asset"):
# `account_asset_management` module also installed in v11 # `account_asset_management` module also installed in v11
# TODO: Merge in existing tables assets if both module installed # TODO: Merge in existing tables assets if both module installed
pass pass
@ -88,9 +94,16 @@ def migrate(env, version):
openupgrade.rename_columns(cr, _column_renames) openupgrade.rename_columns(cr, _column_renames)
openupgrade.rename_fields(env, _field_renames) openupgrade.rename_fields(env, _field_renames)
handle_account_asset_disposal_migration(env) handle_account_asset_disposal_migration(env)
if openupgrade.column_exists(cr, 'account_asset', if openupgrade.column_exists(cr, "account_asset", "analytic_account_id"):
'analytic_account_id'):
# account_asset_analytic module in OCA/account_analytic # account_asset_analytic module in OCA/account_analytic
openupgrade.rename_fields( openupgrade.rename_fields(
env, [('account.asset', 'account_asset', env,
'analytic_account_id', 'account_analytic_id')]) [
(
"account.asset",
"account_asset",
"analytic_account_id",
"account_analytic_id",
)
],
)

View File

@ -7,17 +7,15 @@ from psycopg2 import sql
def create_asset_groups(cr): def create_asset_groups(cr):
# Add a supporting column for indicating the source asset view # Add a supporting column for indicating the source asset view
origin_column = sql.Identifier( origin_column = sql.Identifier(openupgrade.get_legacy_name("view_asset_id"))
openupgrade.get_legacy_name('view_asset_id'))
openupgrade.logged_query( openupgrade.logged_query(
cr, sql.SQL("ALTER TABLE account_asset_group ADD {} int4").format( cr, sql.SQL("ALTER TABLE account_asset_group ADD {} int4").format(origin_column)
origin_column,
),
) )
# Now fill new table recursively attending parents # Now fill new table recursively attending parents
parent_column = sql.Identifier(openupgrade.get_legacy_name('parent_id')) parent_column = sql.Identifier(openupgrade.get_legacy_name("parent_id"))
parent_group_ids = ('NULL', ) parent_group_ids = ("NULL",)
query_sql = sql.SQL(""" query_sql = sql.SQL(
"""
INSERT INTO account_asset_group ( INSERT INTO account_asset_group (
name, code, company_id, parent_id, create_uid, name, code, company_id, parent_id, create_uid,
create_date, write_date, write_uid, {origin_column} create_date, write_date, write_uid, {origin_column}
@ -29,56 +27,54 @@ def create_asset_groups(cr):
ON aag2.{origin_column} = va.{parent_column} ON aag2.{origin_column} = va.{parent_column}
WHERE {parent_column} {rest_sql} WHERE {parent_column} {rest_sql}
RETURNING id RETURNING id
""") """
)
isnull = sql.SQL("IS NULL") isnull = sql.SQL("IS NULL")
inids = sql.SQL("IN %(ids)s") inids = sql.SQL("IN %(ids)s")
while parent_group_ids: while parent_group_ids:
query = query_sql.format( query = query_sql.format(
origin_column=origin_column, origin_column=origin_column,
table=sql.Identifier( table=sql.Identifier(openupgrade.get_legacy_name("account_asset_view")),
openupgrade.get_legacy_name('account_asset_view')
),
parent_column=parent_column, parent_column=parent_column,
rest_sql=isnull if parent_group_ids == ('NULL', ) else inids rest_sql=isnull if parent_group_ids == ("NULL",) else inids,
) )
openupgrade.logged_query(cr, query, {'ids': parent_group_ids}) openupgrade.logged_query(cr, query, {"ids": parent_group_ids})
parent_group_ids = tuple(x[0] for x in cr.fetchall()) parent_group_ids = tuple(x[0] for x in cr.fetchall())
def update_asset_group_links(cr): def update_asset_group_links(cr):
parent_column = sql.Identifier(openupgrade.get_legacy_name('parent_id')) parent_column = sql.Identifier(openupgrade.get_legacy_name("parent_id"))
origin_column = sql.Identifier( origin_column = sql.Identifier(openupgrade.get_legacy_name("view_asset_id"))
openupgrade.get_legacy_name('view_asset_id'))
openupgrade.logged_query( openupgrade.logged_query(
cr, sql.SQL(""" cr,
sql.SQL(
"""
INSERT INTO account_asset_profile_group_rel INSERT INTO account_asset_profile_group_rel
(profile_id, group_id) (profile_id, group_id)
SELECT aap.id, aag.id SELECT aap.id, aag.id
FROM account_asset_profile aap FROM account_asset_profile aap
JOIN account_asset_group aag JOIN account_asset_group aag
ON aag.{origin_column} = aap.{parent_column}""").format( ON aag.{origin_column} = aap.{parent_column}"""
parent_column=parent_column, ).format(parent_column=parent_column, origin_column=origin_column),
origin_column=origin_column,
),
) )
openupgrade.logged_query( openupgrade.logged_query(
cr, sql.SQL(""" cr,
sql.SQL(
"""
INSERT INTO account_asset_group_rel INSERT INTO account_asset_group_rel
(asset_id, group_id) (asset_id, group_id)
SELECT aa.id, aag.id SELECT aa.id, aag.id
FROM account_asset aa FROM account_asset aa
JOIN account_asset_group aag JOIN account_asset_group aag
ON aag.{origin_column} = aa.{parent_column}""").format( ON aag.{origin_column} = aa.{parent_column}"""
parent_column=parent_column, ).format(parent_column=parent_column, origin_column=origin_column),
origin_column=origin_column,
),
) )
@openupgrade.migrate() @openupgrade.migrate()
def migrate(env, version): def migrate(env, version):
column = openupgrade.get_legacy_name('parent_id') column = openupgrade.get_legacy_name("parent_id")
if openupgrade.column_exists(env.cr, 'account_asset', column): if openupgrade.column_exists(env.cr, "account_asset", column):
# if migrating directly from v11 `account_asset` module, there are no # if migrating directly from v11 `account_asset` module, there are no
# view assets nor parents # view assets nor parents
create_asset_groups(env.cr) create_asset_groups(env.cr)

View File

@ -3,14 +3,9 @@
from openupgradelib import openupgrade from openupgradelib import openupgrade
from psycopg2 import sql from psycopg2 import sql
_column_renames = { _column_renames = {
'account_asset_profile': [ "account_asset_profile": [("parent_id", None)],
('parent_id', None), "account_asset": [("parent_id", None)],
],
'account_asset': [
('parent_id', None),
],
} }
@ -18,31 +13,35 @@ def move_view_assets(cr):
"""Copy view assets to other table for preserving them, but outside of the """Copy view assets to other table for preserving them, but outside of the
main table, so remove them from there. main table, so remove them from there.
""" """
temp_table = sql.Identifier( temp_table = sql.Identifier(openupgrade.get_legacy_name("account_asset_view"))
openupgrade.get_legacy_name('account_asset_view'))
openupgrade.logged_query( openupgrade.logged_query(
cr, sql.SQL(""" cr,
sql.SQL(
"""
CREATE TABLE {} AS ( CREATE TABLE {} AS (
SELECT * FROM account_asset SELECT * FROM account_asset
WHERE type='view' WHERE type='view'
)""").format(temp_table), )"""
).format(temp_table),
) )
openupgrade.logged_query(cr, "DELETE FROM account_asset WHERE type='view'") openupgrade.logged_query(cr, "DELETE FROM account_asset WHERE type='view'")
@openupgrade.migrate() @openupgrade.migrate()
def migrate(env, version): def migrate(env, version):
if openupgrade.column_exists(env.cr, 'account_asset', 'parent_id'): if openupgrade.column_exists(env.cr, "account_asset", "parent_id"):
# if migrating directly from v11 `account_asset` module, there are no # if migrating directly from v11 `account_asset` module, there are no
# view assets nor parents # view assets nor parents
openupgrade.rename_columns(env.cr, _column_renames) openupgrade.rename_columns(env.cr, _column_renames)
openupgrade.logged_query( openupgrade.logged_query(
env.cr, """ env.cr,
"""
ALTER TABLE account_asset ALTER TABLE account_asset
DROP CONSTRAINT account_asset_parent_id_fkey""", DROP CONSTRAINT account_asset_parent_id_fkey""",
) )
openupgrade.logged_query( openupgrade.logged_query(
env.cr, """ env.cr,
"""
ALTER TABLE account_asset_profile ALTER TABLE account_asset_profile
DROP CONSTRAINT account_asset_profile_parent_id_fkey""", DROP CONSTRAINT account_asset_profile_parent_id_fkey""",
) )

View File

@ -1,25 +1,30 @@
# Copyright 2009-2017 Noviat # Copyright 2009-2017 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class AccountAccount(models.Model): class AccountAccount(models.Model):
_inherit = 'account.account' _inherit = "account.account"
asset_profile_id = fields.Many2one( asset_profile_id = fields.Many2one(
comodel_name='account.asset.profile', comodel_name="account.asset.profile",
string='Asset Profile', string="Asset Profile",
help="Default Asset Profile when creating invoice lines " help="Default Asset Profile when creating invoice lines " "with this account.",
"with this account.") )
@api.multi @api.multi
@api.constrains('asset_profile_id') @api.constrains("asset_profile_id")
def _check_asset_profile(self): def _check_asset_profile(self):
for account in self: for account in self:
if account.asset_profile_id and \ if (
account.asset_profile_id.account_asset_id != account: account.asset_profile_id
raise ValidationError(_( and account.asset_profile_id.account_asset_id != account
"The Asset Account defined in the Asset Profile " ):
"must be equal to the account.")) raise ValidationError(
_(
"The Asset Account defined in the Asset Profile "
"must be equal to the account."
)
)

File diff suppressed because it is too large Load Diff

View File

@ -6,24 +6,26 @@ from odoo import api, fields, models
class AccountAssetGroup(models.Model): class AccountAssetGroup(models.Model):
_name = 'account.asset.group' _name = "account.asset.group"
_description = 'Asset Group' _description = "Asset Group"
_order = 'name' _order = "name"
_parent_store = True _parent_store = True
name = fields.Char(string='Name', size=64, required=True, index=True) name = fields.Char(string="Name", size=64, required=True, index=True)
code = fields.Char(index=True) code = fields.Char(index=True)
parent_path = fields.Char(index=True) parent_path = fields.Char(index=True)
company_id = fields.Many2one( company_id = fields.Many2one(
comodel_name='res.company', comodel_name="res.company",
string='Company', string="Company",
required=True, required=True,
default=lambda self: self._default_company_id()) default=lambda self: self._default_company_id(),
)
parent_id = fields.Many2one( parent_id = fields.Many2one(
comodel_name='account.asset.group', comodel_name="account.asset.group",
string='Parent Asset Group', string="Parent Asset Group",
ondelete='restrict') ondelete="restrict",
)
@api.model @api.model
def _default_company_id(self): def _default_company_id(self):
return self.env['res.company']._company_default_get('account.asset') return self.env["res.company"]._company_default_get("account.asset")

View File

@ -1,87 +1,86 @@
# Copyright 2009-2018 Noviat # Copyright 2009-2018 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
import odoo.addons.decimal_precision as dp
from odoo.exceptions import UserError from odoo.exceptions import UserError
import odoo.addons.decimal_precision as dp
class AccountAssetLine(models.Model): class AccountAssetLine(models.Model):
_name = 'account.asset.line' _name = "account.asset.line"
_description = 'Asset depreciation table line' _description = "Asset depreciation table line"
_order = 'type, line_date' _order = "type, line_date"
name = fields.Char(string='Depreciation Name', size=64, readonly=True) name = fields.Char(string="Depreciation Name", size=64, readonly=True)
asset_id = fields.Many2one( asset_id = fields.Many2one(
comodel_name='account.asset', string='Asset', comodel_name="account.asset", string="Asset", required=True, ondelete="cascade"
required=True, ondelete='cascade') )
previous_id = fields.Many2one( previous_id = fields.Many2one(
comodel_name='account.asset.line', comodel_name="account.asset.line",
string='Previous Depreciation Line', string="Previous Depreciation Line",
readonly=True)
parent_state = fields.Selection(
related='asset_id.state',
string='State of Asset',
readonly=True, readonly=True,
) )
parent_state = fields.Selection(
related="asset_id.state", string="State of Asset", readonly=True
)
depreciation_base = fields.Float( depreciation_base = fields.Float(
related='asset_id.depreciation_base', related="asset_id.depreciation_base", string="Depreciation Base", readonly=True
string='Depreciation Base',
readonly=True,
) )
amount = fields.Float( amount = fields.Float(
string='Amount', digits=dp.get_precision('Account'), string="Amount", digits=dp.get_precision("Account"), required=True
required=True)
remaining_value = fields.Float(
compute='_compute_values',
digits=dp.get_precision('Account'),
string='Next Period Depreciation',
store=True)
depreciated_value = fields.Float(
compute='_compute_values',
digits=dp.get_precision('Account'),
string='Amount Already Depreciated',
store=True)
line_date = fields.Date(string='Date', required=True)
line_days = fields.Integer(
string='Days',
readonly=True,
) )
remaining_value = fields.Float(
compute="_compute_values",
digits=dp.get_precision("Account"),
string="Next Period Depreciation",
store=True,
)
depreciated_value = fields.Float(
compute="_compute_values",
digits=dp.get_precision("Account"),
string="Amount Already Depreciated",
store=True,
)
line_date = fields.Date(string="Date", required=True)
line_days = fields.Integer(string="Days", readonly=True)
move_id = fields.Many2one( move_id = fields.Many2one(
comodel_name='account.move', comodel_name="account.move", string="Depreciation Entry", readonly=True
string='Depreciation Entry', readonly=True) )
move_check = fields.Boolean( move_check = fields.Boolean(
compute='_compute_move_check', compute="_compute_move_check", string="Posted", store=True
string='Posted', )
store=True)
type = fields.Selection( type = fields.Selection(
selection=[ selection=[
('create', 'Depreciation Base'), ("create", "Depreciation Base"),
('depreciate', 'Depreciation'), ("depreciate", "Depreciation"),
('remove', 'Asset Removal')], ("remove", "Asset Removal"),
readonly=True, default='depreciate') ],
readonly=True,
default="depreciate",
)
init_entry = fields.Boolean( init_entry = fields.Boolean(
string='Initial Balance Entry', string="Initial Balance Entry",
help="Set this flag for entries of previous fiscal years " help="Set this flag for entries of previous fiscal years "
"for which Odoo has not generated accounting entries.") "for which Odoo has not generated accounting entries.",
)
@api.depends('amount', 'previous_id', 'type') @api.depends("amount", "previous_id", "type")
@api.multi @api.multi
def _compute_values(self): def _compute_values(self):
dlines = self dlines = self
if self.env.context.get('no_compute_asset_line_ids'): if self.env.context.get("no_compute_asset_line_ids"):
# skip compute for lines in unlink # skip compute for lines in unlink
exclude_ids = self.env.context['no_compute_asset_line_ids'] exclude_ids = self.env.context["no_compute_asset_line_ids"]
dlines = self.filtered(lambda l: l.id not in exclude_ids) dlines = self.filtered(lambda l: l.id not in exclude_ids)
dlines = dlines.filtered(lambda l: l.type == 'depreciate') dlines = dlines.filtered(lambda l: l.type == "depreciate")
dlines = dlines.sorted(key=lambda l: l.line_date) dlines = dlines.sorted(key=lambda l: l.line_date)
# Group depreciation lines per asset # Group depreciation lines per asset
asset_ids = dlines.mapped('asset_id') asset_ids = dlines.mapped("asset_id")
grouped_dlines = [] grouped_dlines = []
for asset in asset_ids: for asset in asset_ids:
grouped_dlines.append( grouped_dlines.append(dlines.filtered(lambda l: l.asset_id.id == asset.id))
dlines.filtered(lambda l: l.asset_id.id == asset.id))
for dlines in grouped_dlines: for dlines in grouped_dlines:
for i, dl in enumerate(dlines): for i, dl in enumerate(dlines):
@ -89,102 +88,125 @@ class AccountAssetLine(models.Model):
depreciation_base = dl.depreciation_base depreciation_base = dl.depreciation_base
tmp = depreciation_base - dl.previous_id.remaining_value tmp = depreciation_base - dl.previous_id.remaining_value
depreciated_value = dl.previous_id and tmp or 0.0 depreciated_value = dl.previous_id and tmp or 0.0
remaining_value = \ remaining_value = depreciation_base - depreciated_value - dl.amount
depreciation_base - depreciated_value - dl.amount
else: else:
depreciated_value += dl.previous_id.amount depreciated_value += dl.previous_id.amount
remaining_value -= dl.amount remaining_value -= dl.amount
dl.depreciated_value = depreciated_value dl.depreciated_value = depreciated_value
dl.remaining_value = remaining_value dl.remaining_value = remaining_value
@api.depends('move_id') @api.depends("move_id")
@api.multi @api.multi
def _compute_move_check(self): def _compute_move_check(self):
for line in self: for line in self:
line.move_check = bool(line.move_id) line.move_check = bool(line.move_id)
@api.onchange('amount') @api.onchange("amount")
def _onchange_amount(self): def _onchange_amount(self):
if self.type == 'depreciate': if self.type == "depreciate":
self.remaining_value = self.depreciation_base - \ self.remaining_value = (
self.depreciated_value - self.amount self.depreciation_base - self.depreciated_value - self.amount
)
@api.multi @api.multi
def write(self, vals): def write(self, vals):
for dl in self: for dl in self:
line_date = vals.get('line_date') or dl.line_date line_date = vals.get("line_date") or dl.line_date
asset_lines = dl.asset_id.depreciation_line_ids asset_lines = dl.asset_id.depreciation_line_ids
if list(vals.keys()) == ['move_id'] and not vals['move_id']: if list(vals.keys()) == ["move_id"] and not vals["move_id"]:
# allow to remove an accounting entry via the # allow to remove an accounting entry via the
# 'Delete Move' button on the depreciation lines. # 'Delete Move' button on the depreciation lines.
if not self.env.context.get('unlink_from_asset'): if not self.env.context.get("unlink_from_asset"):
raise UserError(_( raise UserError(
"You are not allowed to remove an accounting entry " _(
"linked to an asset." "You are not allowed to remove an accounting entry "
"\nYou should remove such entries from the asset.")) "linked to an asset."
elif list(vals.keys()) == ['asset_id']: "\nYou should remove such entries from the asset."
)
)
elif list(vals.keys()) == ["asset_id"]:
continue continue
elif dl.move_id and not self.env.context.get( elif dl.move_id and not self.env.context.get("allow_asset_line_update"):
'allow_asset_line_update'): raise UserError(
raise UserError(_( _(
"You cannot change a depreciation line " "You cannot change a depreciation line "
"with an associated accounting entry.")) "with an associated accounting entry."
elif vals.get('init_entry'): )
)
elif vals.get("init_entry"):
check = asset_lines.filtered( check = asset_lines.filtered(
lambda l: l.move_check and l.type == 'depreciate' and lambda l: l.move_check
l.line_date <= line_date) and l.type == "depreciate"
and l.line_date <= line_date
)
if check: if check:
raise UserError(_( raise UserError(
"You cannot set the 'Initial Balance Entry' flag " _(
"on a depreciation line " "You cannot set the 'Initial Balance Entry' flag "
"with prior posted entries.")) "on a depreciation line "
elif vals.get('line_date'): "with prior posted entries."
if dl.type == 'create': )
)
elif vals.get("line_date"):
if dl.type == "create":
check = asset_lines.filtered( check = asset_lines.filtered(
lambda l: l.type != 'create' and lambda l: l.type != "create"
(l.init_entry or l.move_check) and and (l.init_entry or l.move_check)
l.line_date < fields.Date.to_date(vals['line_date'])) and l.line_date < fields.Date.to_date(vals["line_date"])
)
if check: if check:
raise UserError( raise UserError(
_("You cannot set the Asset Start Date " _(
"after already posted entries.")) "You cannot set the Asset Start Date "
"after already posted entries."
)
)
else: else:
check = asset_lines.filtered( check = asset_lines.filtered(
lambda l: l != dl and lambda l: l != dl
(l.init_entry or l.move_check) and and (l.init_entry or l.move_check)
l.line_date > fields.Date.to_date(vals['line_date'])) and l.line_date > fields.Date.to_date(vals["line_date"])
)
if check: if check:
raise UserError(_( raise UserError(
"You cannot set the date on a depreciation line " _(
"prior to already posted entries.")) "You cannot set the date on a depreciation line "
"prior to already posted entries."
)
)
return super().write(vals) return super().write(vals)
@api.multi @api.multi
def unlink(self): def unlink(self):
for dl in self: for dl in self:
if dl.type == 'create' and dl.amount: if dl.type == "create" and dl.amount:
raise UserError(_( raise UserError(
"You cannot remove an asset line " _("You cannot remove an asset line " "of type 'Depreciation Base'.")
"of type 'Depreciation Base'.")) )
elif dl.move_id: elif dl.move_id:
raise UserError(_( raise UserError(
"You cannot delete a depreciation line with " _(
"an associated accounting entry.")) "You cannot delete a depreciation line with "
"an associated accounting entry."
)
)
previous = dl.previous_id previous = dl.previous_id
next_line = dl.asset_id.depreciation_line_ids.filtered( next_line = dl.asset_id.depreciation_line_ids.filtered(
lambda l: l.previous_id == dl and l not in self) lambda l: l.previous_id == dl and l not in self
)
if next_line: if next_line:
next_line.previous_id = previous next_line.previous_id = previous
return super(AccountAssetLine, self.with_context( return super(
no_compute_asset_line_ids=self.ids)).unlink() AccountAssetLine, self.with_context(no_compute_asset_line_ids=self.ids)
).unlink()
def _setup_move_data(self, depreciation_date): def _setup_move_data(self, depreciation_date):
asset = self.asset_id asset = self.asset_id
move_data = { move_data = {
'name': asset.name, "name": asset.name,
'date': depreciation_date, "date": depreciation_date,
'ref': self.name, "ref": self.name,
'journal_id': asset.profile_id.journal_id.id, "journal_id": asset.profile_id.journal_id.id,
} }
return move_data return move_data
@ -192,25 +214,25 @@ class AccountAssetLine(models.Model):
asset = self.asset_id asset = self.asset_id
amount = self.amount amount = self.amount
analytic_id = False analytic_id = False
if ml_type == 'depreciation': if ml_type == "depreciation":
debit = amount < 0 and -amount or 0.0 debit = amount < 0 and -amount or 0.0
credit = amount > 0 and amount or 0.0 credit = amount > 0 and amount or 0.0
elif ml_type == 'expense': elif ml_type == "expense":
debit = amount > 0 and amount or 0.0 debit = amount > 0 and amount or 0.0
credit = amount < 0 and -amount or 0.0 credit = amount < 0 and -amount or 0.0
analytic_id = asset.account_analytic_id.id analytic_id = asset.account_analytic_id.id
move_line_data = { move_line_data = {
'name': asset.name, "name": asset.name,
'ref': self.name, "ref": self.name,
'move_id': move.id, "move_id": move.id,
'account_id': account.id, "account_id": account.id,
'credit': credit, "credit": credit,
'debit': debit, "debit": debit,
'journal_id': asset.profile_id.journal_id.id, "journal_id": asset.profile_id.journal_id.id,
'partner_id': asset.partner_id.id, "partner_id": asset.partner_id.id,
'analytic_account_id': analytic_id, "analytic_account_id": analytic_id,
'date': depreciation_date, "date": depreciation_date,
'asset_id': asset.id, "asset_id": asset.id,
} }
return move_line_data return move_line_data
@ -218,63 +240,58 @@ class AccountAssetLine(models.Model):
def create_move(self): def create_move(self):
created_move_ids = [] created_move_ids = []
asset_ids = set() asset_ids = set()
ctx = dict(self.env.context, ctx = dict(self.env.context, allow_asset=True, check_move_validity=False)
allow_asset=True, check_move_validity=False)
for line in self: for line in self:
asset = line.asset_id asset = line.asset_id
depreciation_date = line.line_date depreciation_date = line.line_date
am_vals = line._setup_move_data(depreciation_date) am_vals = line._setup_move_data(depreciation_date)
move = self.env['account.move'].with_context(ctx).create(am_vals) move = self.env["account.move"].with_context(ctx).create(am_vals)
depr_acc = asset.profile_id.account_depreciation_id depr_acc = asset.profile_id.account_depreciation_id
exp_acc = asset.profile_id.account_expense_depreciation_id exp_acc = asset.profile_id.account_expense_depreciation_id
aml_d_vals = line._setup_move_line_data( aml_d_vals = line._setup_move_line_data(
depreciation_date, depr_acc, 'depreciation', move) depreciation_date, depr_acc, "depreciation", move
self.env['account.move.line'].with_context(ctx).create(aml_d_vals) )
self.env["account.move.line"].with_context(ctx).create(aml_d_vals)
aml_e_vals = line._setup_move_line_data( aml_e_vals = line._setup_move_line_data(
depreciation_date, exp_acc, 'expense', move) depreciation_date, exp_acc, "expense", move
self.env['account.move.line'].with_context(ctx).create(aml_e_vals) )
self.env["account.move.line"].with_context(ctx).create(aml_e_vals)
move.post() move.post()
line.with_context(allow_asset_line_update=True).write({ line.with_context(allow_asset_line_update=True).write({"move_id": move.id})
'move_id': move.id
})
created_move_ids.append(move.id) created_move_ids.append(move.id)
asset_ids.add(asset.id) asset_ids.add(asset.id)
# we re-evaluate the assets to determine if we can close them # we re-evaluate the assets to determine if we can close them
for asset in self.env['account.asset'].browse(list(asset_ids)): for asset in self.env["account.asset"].browse(list(asset_ids)):
if asset.company_currency_id.is_zero(asset.value_residual): if asset.company_currency_id.is_zero(asset.value_residual):
asset.state = 'close' asset.state = "close"
return created_move_ids return created_move_ids
@api.multi @api.multi
def open_move(self): def open_move(self):
self.ensure_one() self.ensure_one()
return { return {
'name': _("Journal Entry"), "name": _("Journal Entry"),
'view_type': 'form', "view_type": "form",
'view_mode': 'tree,form', "view_mode": "tree,form",
'res_model': 'account.move', "res_model": "account.move",
'view_id': False, "view_id": False,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'context': self.env.context, "context": self.env.context,
'domain': [('id', '=', self.move_id.id)], "domain": [("id", "=", self.move_id.id)],
} }
@api.multi @api.multi
def unlink_move(self): def unlink_move(self):
for line in self: for line in self:
move = line.move_id move = line.move_id
if move.state == 'posted': if move.state == "posted":
move.button_cancel() move.button_cancel()
move.with_context(unlink_from_asset=True).unlink() move.with_context(unlink_from_asset=True).unlink()
# trigger store function # trigger store function
line.with_context(unlink_from_asset=True).write( line.with_context(unlink_from_asset=True).write({"move_id": False})
{'move_id': False}) if line.parent_state == "close":
if line.parent_state == 'close': line.asset_id.write({"state": "open"})
line.asset_id.write({'state': 'open'}) elif line.parent_state == "removed" and line.type == "remove":
elif line.parent_state == 'removed' and line.type == 'remove': line.asset_id.write({"state": "close", "date_remove": False})
line.asset_id.write({
'state': 'close',
'date_remove': False,
})
line.unlink() line.unlink()
return True return True

View File

@ -1,150 +1,169 @@
# Copyright 2009-2018 Noviat # Copyright 2009-2018 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
class AccountAssetProfile(models.Model): class AccountAssetProfile(models.Model):
_name = 'account.asset.profile' _name = "account.asset.profile"
_description = 'Asset profile' _description = "Asset profile"
_order = 'name' _order = "name"
name = fields.Char(string='Name', size=64, required=True, index=True) name = fields.Char(string="Name", size=64, required=True, index=True)
note = fields.Text() note = fields.Text()
account_analytic_id = fields.Many2one( account_analytic_id = fields.Many2one(
comodel_name='account.analytic.account', comodel_name="account.analytic.account", string="Analytic account"
string='Analytic account') )
account_asset_id = fields.Many2one( account_asset_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
string='Asset Account', required=True) string="Asset Account",
required=True,
)
account_depreciation_id = fields.Many2one( account_depreciation_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
string='Depreciation Account', required=True) string="Depreciation Account",
required=True,
)
account_expense_depreciation_id = fields.Many2one( account_expense_depreciation_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
string='Depr. Expense Account', required=True) string="Depr. Expense Account",
required=True,
)
account_plus_value_id = fields.Many2one( account_plus_value_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
string='Plus-Value Account') string="Plus-Value Account",
)
account_min_value_id = fields.Many2one( account_min_value_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
string='Min-Value Account') string="Min-Value Account",
)
account_residual_value_id = fields.Many2one( account_residual_value_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
string='Residual Value Account') string="Residual Value Account",
)
journal_id = fields.Many2one( journal_id = fields.Many2one(
comodel_name='account.journal', comodel_name="account.journal",
domain=[('type', '=', 'general')], domain=[("type", "=", "general")],
string='Journal', required=True) string="Journal",
required=True,
)
company_id = fields.Many2one( company_id = fields.Many2one(
comodel_name='res.company', comodel_name="res.company",
string='Company', required=True, string="Company",
default=lambda self: self._default_company_id()) required=True,
default=lambda self: self._default_company_id(),
)
group_ids = fields.Many2many( group_ids = fields.Many2many(
comodel_name='account.asset.group', comodel_name="account.asset.group",
relation="account_asset_profile_group_rel", relation="account_asset_profile_group_rel",
column1="profile_id", column1="profile_id",
column2="group_id", column2="group_id",
string='Asset Groups') string="Asset Groups",
)
method = fields.Selection( method = fields.Selection(
selection=lambda self: self._selection_method(), selection=lambda self: self._selection_method(),
string='Computation Method', string="Computation Method",
required=True, required=True,
help="Choose the method to use to compute the depreciation lines.\n" help="Choose the method to use to compute the depreciation lines.\n"
" * Linear: Calculated on basis of: " " * Linear: Calculated on basis of: "
"Depreciation Base / Number of Depreciations. " "Depreciation Base / Number of Depreciations. "
"Depreciation Base = Purchase Value - Salvage Value.\n" "Depreciation Base = Purchase Value - Salvage Value.\n"
" * Linear-Limit: Linear up to Salvage Value. " " * Linear-Limit: Linear up to Salvage Value. "
"Depreciation Base = Purchase Value.\n" "Depreciation Base = Purchase Value.\n"
" * Degressive: Calculated on basis of: " " * Degressive: Calculated on basis of: "
"Residual Value * Degressive Factor.\n" "Residual Value * Degressive Factor.\n"
" * Degressive-Linear (only for Time Method = Year): " " * Degressive-Linear (only for Time Method = Year): "
"Degressive becomes linear when the annual linear " "Degressive becomes linear when the annual linear "
"depreciation exceeds the annual degressive depreciation.\n" "depreciation exceeds the annual degressive depreciation.\n"
" * Degressive-Limit: Degressive up to Salvage Value. " " * Degressive-Limit: Degressive up to Salvage Value. "
"The Depreciation Base is equal to the asset value.", "The Depreciation Base is equal to the asset value.",
default='linear') default="linear",
)
method_number = fields.Integer( method_number = fields.Integer(
string='Number of Years', string="Number of Years",
help="The number of years needed to depreciate your asset", help="The number of years needed to depreciate your asset",
default=5) default=5,
)
method_period = fields.Selection( method_period = fields.Selection(
selection=lambda self: self._selection_method_period(), selection=lambda self: self._selection_method_period(),
string='Period Length', required=True, string="Period Length",
default='year', required=True,
help="Period length for the depreciation accounting entries") default="year",
method_progress_factor = fields.Float( help="Period length for the depreciation accounting entries",
string='Degressive Factor', default=0.3) )
method_progress_factor = fields.Float(string="Degressive Factor", default=0.3)
method_time = fields.Selection( method_time = fields.Selection(
selection=lambda self: self._selection_method_time(), selection=lambda self: self._selection_method_time(),
string='Time Method', required=True, string="Time Method",
default='year', required=True,
default="year",
help="Choose the method to use to compute the dates and " help="Choose the method to use to compute the dates and "
"number of depreciation lines.\n" "number of depreciation lines.\n"
" * Number of Years: Specify the number of years " " * Number of Years: Specify the number of years "
"for the depreciation.\n") "for the depreciation.\n",
)
days_calc = fields.Boolean( days_calc = fields.Boolean(
string='Calculate by days', string="Calculate by days",
default=False, default=False,
help="Use number of days to calculate depreciation amount") help="Use number of days to calculate depreciation amount",
)
use_leap_years = fields.Boolean( use_leap_years = fields.Boolean(
string='Use leap years', string="Use leap years",
default=False, default=False,
help="If not set, the system will distribute evenly the amount to " help="If not set, the system will distribute evenly the amount to "
"amortize across the years, based on the number of years. " "amortize across the years, based on the number of years. "
"So the amount per year will be the " "So the amount per year will be the "
"depreciation base / number of years.\n " "depreciation base / number of years.\n "
"If set, the system will consider if the current year " "If set, the system will consider if the current year "
"is a leap year. The amount to depreciate per year will be " "is a leap year. The amount to depreciate per year will be "
"calculated as depreciation base / (depreciation end date - " "calculated as depreciation base / (depreciation end date - "
"start date + 1) * days in the current year.", "start date + 1) * days in the current year.",
) )
prorata = fields.Boolean( prorata = fields.Boolean(
string='Prorata Temporis', string="Prorata Temporis",
help="Indicates that the first depreciation entry for this asset " help="Indicates that the first depreciation entry for this asset "
"has to be done from the depreciation start date instead of " "has to be done from the depreciation start date instead of "
"the first day of the fiscal year.") "the first day of the fiscal year.",
)
open_asset = fields.Boolean( open_asset = fields.Boolean(
string='Skip Draft State', string="Skip Draft State",
help="Check this if you want to automatically confirm the assets " help="Check this if you want to automatically confirm the assets "
"of this profile when created by invoices.") "of this profile when created by invoices.",
)
asset_product_item = fields.Boolean( asset_product_item = fields.Boolean(
string='Create an asset by product item', string="Create an asset by product item",
help="By default during the validation of an invoice, an asset " help="By default during the validation of an invoice, an asset "
"is created by invoice line as long as an accounting entry is " "is created by invoice line as long as an accounting entry is "
"created by invoice line. " "created by invoice line. "
"With this setting, an accounting entry will be created by " "With this setting, an accounting entry will be created by "
"product item. So, there will be an asset by product item.") "product item. So, there will be an asset by product item.",
)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
@api.model @api.model
def _default_company_id(self): def _default_company_id(self):
return self.env['res.company']._company_default_get('account.asset') return self.env["res.company"]._company_default_get("account.asset")
@api.model @api.model
def _selection_method(self): def _selection_method(self):
return[ return [
('linear', _('Linear')), ("linear", _("Linear")),
('linear-limit', _('Linear up to Salvage Value')), ("linear-limit", _("Linear up to Salvage Value")),
('degressive', _('Degressive')), ("degressive", _("Degressive")),
('degr-linear', _('Degressive-Linear')), ("degr-linear", _("Degressive-Linear")),
('degr-limit', _('Degressive up to Salvage Value')), ("degr-limit", _("Degressive up to Salvage Value")),
] ]
@api.model @api.model
def _selection_method_period(self): def _selection_method_period(self):
return [ return [("month", _("Month")), ("quarter", _("Quarter")), ("year", _("Year"))]
('month', _('Month')),
('quarter', _('Quarter')),
('year', _('Year')),
]
@api.model @api.model
def _selection_method_time(self): def _selection_method_time(self):
@ -152,47 +171,43 @@ class AccountAssetProfile(models.Model):
Install the 'account_asset_management_method_number_end' to enable the Install the 'account_asset_management_method_number_end' to enable the
'Number' and 'End' Time Methods. 'Number' and 'End' Time Methods.
""" """
return [ return [("year", _("Number of Years or end date"))]
('year', _('Number of Years or end date')),
]
@api.multi @api.multi
@api.constrains('method') @api.constrains("method")
def _check_method(self): def _check_method(self):
for profile in self: for profile in self:
if profile.method == 'degr-linear' and \ if profile.method == "degr-linear" and profile.method_time != "year":
profile.method_time != 'year':
raise UserError( raise UserError(
_("Degressive-Linear is only supported for Time Method = " _("Degressive-Linear is only supported for Time Method = " "Year.")
"Year.")) )
@api.onchange('method_time') @api.onchange("method_time")
def _onchange_method_time(self): def _onchange_method_time(self):
if self.method_time != 'year': if self.method_time != "year":
self.prorata = True self.prorata = True
@api.model @api.model
def create(self, vals): def create(self, vals):
if vals.get('method_time') != 'year' and not vals.get('prorata'): if vals.get("method_time") != "year" and not vals.get("prorata"):
vals['prorata'] = True vals["prorata"] = True
profile = super().create(vals) profile = super().create(vals)
acc_id = vals.get('account_asset_id') acc_id = vals.get("account_asset_id")
if acc_id: if acc_id:
account = self.env['account.account'].browse(acc_id) account = self.env["account.account"].browse(acc_id)
if not account.asset_profile_id: if not account.asset_profile_id:
account.write({'asset_profile_id': profile.id}) account.write({"asset_profile_id": profile.id})
return profile return profile
@api.multi @api.multi
def write(self, vals): def write(self, vals):
if vals.get('method_time'): if vals.get("method_time"):
if vals['method_time'] != 'year' and not vals.get('prorata'): if vals["method_time"] != "year" and not vals.get("prorata"):
vals['prorata'] = True vals["prorata"] = True
res = super().write(vals) res = super().write(vals)
# TODO last profile in self is defined as default on the related # TODO last profile in self is defined as default on the related
# account. must be improved. # account. must be improved.
account = self.env['account.account'].browse( account = self.env["account.account"].browse(vals.get("account_asset_id"))
vals.get('account_asset_id'))
if self and account and not account.asset_profile_id: if self and account and not account.asset_profile_id:
account.write({'asset_profile_id': self[-1].id}) account.write({"asset_profile_id": self[-1].id})
return res return res

View File

@ -5,21 +5,20 @@ from odoo import fields, models
class AccountAssetRecomputeTrigger(models.Model): class AccountAssetRecomputeTrigger(models.Model):
_name = 'account.asset.recompute.trigger' _name = "account.asset.recompute.trigger"
_description = "Asset table recompute triggers" _description = "Asset table recompute triggers"
reason = fields.Char( reason = fields.Char(string="Reason", required=True)
string='Reason', required=True) company_id = fields.Many2one("res.company", string="Company", required=True)
company_id = fields.Many2one(
'res.company', string='Company', required=True)
date_trigger = fields.Datetime( date_trigger = fields.Datetime(
'Trigger Date', "Trigger Date",
readonly=True, readonly=True,
help="Date of the event triggering the need to " help="Date of the event triggering the need to " "recompute the Asset Tables.",
"recompute the Asset Tables.") )
date_completed = fields.Datetime( date_completed = fields.Datetime("Completion Date", readonly=True)
'Completion Date', readonly=True)
state = fields.Selection( state = fields.Selection(
selection=[('open', 'Open'), ('done', 'Done')], selection=[("open", "Open"), ("done", "Done")],
string='State', default='open', string="State",
readonly=True) default="open",
readonly=True,
)

View File

@ -4,6 +4,7 @@
import logging import logging
import time import time
from datetime import datetime from datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import api, models from odoo import api, models
@ -13,37 +14,33 @@ _logger = logging.getLogger(__name__)
class AccountFiscalYear(models.Model): class AccountFiscalYear(models.Model):
_inherit = 'account.fiscal.year' _inherit = "account.fiscal.year"
@api.model @api.model
def create(self, vals): def create(self, vals):
date_from = datetime.strptime(vals.get('date_from'), '%Y-%m-%d') date_from = datetime.strptime(vals.get("date_from"), "%Y-%m-%d")
date_to = datetime.strptime(vals.get('date_to'), '%Y-%m-%d') date_to = datetime.strptime(vals.get("date_to"), "%Y-%m-%d")
if not date_to == date_from + relativedelta(years=1, days=-1): if not date_to == date_from + relativedelta(years=1, days=-1):
recompute_vals = { recompute_vals = {
'reason': 'creation of fiscalyear %s' % vals.get('name'), "reason": "creation of fiscalyear %s" % vals.get("name"),
'company_id': "company_id": vals.get("company_id") or self.env.user.company_id.id,
vals.get('company_id') or "date_trigger": time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
self.env.user.company_id.id, "state": "open",
'date_trigger': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'state': 'open',
} }
self.env['account.asset.recompute.trigger'].sudo().create( self.env["account.asset.recompute.trigger"].sudo().create(recompute_vals)
recompute_vals)
return super().create(vals) return super().create(vals)
@api.multi @api.multi
def write(self, vals): def write(self, vals):
if vals.get('date_from') or vals.get('date_to'): if vals.get("date_from") or vals.get("date_to"):
for fy in self: for fy in self:
recompute_vals = { recompute_vals = {
'reason': "reason": "duration change of fiscalyear %s" % fy.name,
'duration change of fiscalyear %s' % fy.name, "company_id": fy.company_id.id,
'company_id': fy.company_id.id, "date_trigger": time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'date_trigger': "state": "open",
time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'state': 'open',
} }
self.env['account.asset.recompute.trigger'].sudo().\ self.env["account.asset.recompute.trigger"].sudo().create(
create(recompute_vals) recompute_vals
)
return super().write(vals) return super().write(vals)

View File

@ -7,7 +7,7 @@ from odoo import api, fields, models
class AccountInvoice(models.Model): class AccountInvoice(models.Model):
_inherit = 'account.invoice' _inherit = "account.invoice"
@api.multi @api.multi
def finalize_invoice_move_lines(self, move_lines): def finalize_invoice_move_lines(self, move_lines):
@ -15,27 +15,32 @@ class AccountInvoice(models.Model):
new_lines = [] new_lines = []
for line_tuple in move_lines: for line_tuple in move_lines:
line = line_tuple[2] line = line_tuple[2]
dp = self.env['decimal.precision'] dp = self.env["decimal.precision"]
if line.get('asset_profile_id') and \ if line.get("asset_profile_id") and line.get("quantity", 0.0) > 1.0:
line.get('quantity', 0.0) > 1.0: profile = self.env["account.asset.profile"].browse(
profile = self.env['account.asset.profile'].browse( [line.get("asset_profile_id")]
[line.get('asset_profile_id')]) )
if profile.asset_product_item: if profile.asset_product_item:
origin_line = copy.deepcopy(line) origin_line = copy.deepcopy(line)
line_qty = line.get('quantity') line_qty = line.get("quantity")
line['quantity'] = round(line['quantity'] / line_qty, line["quantity"] = round(
dp.precision_get('Account')) line["quantity"] / line_qty, dp.precision_get("Account")
line['debit'] = round(line['debit'] / line_qty, )
dp.precision_get('Account')) line["debit"] = round(
line['credit'] = round(line['credit'] / line_qty, line["debit"] / line_qty, dp.precision_get("Account")
dp.precision_get('Account')) )
for analytic_line_tuple in line['analytic_line_ids']: line["credit"] = round(
line["credit"] / line_qty, dp.precision_get("Account")
)
for analytic_line_tuple in line["analytic_line_ids"]:
analytic_line = analytic_line_tuple[2] analytic_line = analytic_line_tuple[2]
analytic_line['amount'] = round( analytic_line["amount"] = round(
analytic_line['amount'] / line_qty, analytic_line["amount"] / line_qty,
dp.precision_get('Account')) dp.precision_get("Account"),
analytic_line['unit_amount'] = round( )
analytic_line['unit_amount'] / line_qty, 2) analytic_line["unit_amount"] = round(
analytic_line["unit_amount"] / line_qty, 2
)
line_to_create = line_qty line_to_create = line_qty
while line_to_create > 1: while line_to_create > 1:
line_to_create -= 1 line_to_create -= 1
@ -43,29 +48,31 @@ class AccountInvoice(models.Model):
new_lines.append(new_line) new_lines.append(new_line)
# Compute rounding difference and apply it on the first # Compute rounding difference and apply it on the first
# line # line
line['quantity'] += round( line["quantity"] += round(
origin_line['quantity'] - line['quantity'] * line_qty, origin_line["quantity"] - line["quantity"] * line_qty, 2
2) )
line['debit'] += round( line["debit"] += round(
origin_line['debit'] - line['debit'] * line_qty, origin_line["debit"] - line["debit"] * line_qty,
dp.precision_get('Account')) dp.precision_get("Account"),
line['credit'] += round( )
origin_line['credit'] - line['credit'] * line_qty, line["credit"] += round(
dp.precision_get('Account')) origin_line["credit"] - line["credit"] * line_qty,
dp.precision_get("Account"),
)
i = 0 i = 0
for analytic_line_tuple in line['analytic_line_ids']: for analytic_line_tuple in line["analytic_line_ids"]:
analytic_line = analytic_line_tuple[2] analytic_line = analytic_line_tuple[2]
origin_analytic_line = \ origin_analytic_line = origin_line["analytic_line_ids"][i][2]
origin_line['analytic_line_ids'][i][2] analytic_line["amount"] += round(
analytic_line['amount'] += round( origin_analytic_line["amount"]
origin_analytic_line['amount'] - analytic_line[ - analytic_line["amount"] * line_qty,
'amount'] * line_qty, dp.precision_get("Account"),
dp.precision_get('Account')) )
analytic_line['unit_amount'] += round( analytic_line["unit_amount"] += round(
origin_analytic_line['unit_amount'] - origin_analytic_line["unit_amount"]
analytic_line[ - analytic_line["unit_amount"] * line_qty,
'unit_amount'] * line_qty, dp.precision_get("Account"),
dp.precision_get('Account')) )
i += 1 i += 1
move_lines.extend(new_lines) move_lines.extend(new_lines)
return move_lines return move_lines
@ -74,21 +81,21 @@ class AccountInvoice(models.Model):
def action_move_create(self): def action_move_create(self):
res = super().action_move_create() res = super().action_move_create()
for inv in self: for inv in self:
assets = inv.move_id.line_ids.mapped('asset_id') assets = inv.move_id.line_ids.mapped("asset_id")
for asset in assets: for asset in assets:
asset.code = inv.move_name asset.code = inv.move_name
asset_line_name = asset._get_depreciation_entry_name(0) asset_line_name = asset._get_depreciation_entry_name(0)
asset.depreciation_line_ids[0].with_context( asset.depreciation_line_ids[0].with_context(
{'allow_asset_line_update': True} {"allow_asset_line_update": True}
).name = asset_line_name ).name = asset_line_name
return res return res
@api.multi @api.multi
def action_cancel(self): def action_cancel(self):
assets = self.env['account.asset'] assets = self.env["account.asset"]
for inv in self: for inv in self:
move = inv.move_id move = inv.move_id
assets |= move.line_ids.mapped('asset_id') assets |= move.line_ids.mapped("asset_id")
super().action_cancel() super().action_cancel()
if assets: if assets:
assets.unlink() assets.unlink()
@ -97,47 +104,47 @@ class AccountInvoice(models.Model):
@api.model @api.model
def line_get_convert(self, line, part): def line_get_convert(self, line, part):
res = super().line_get_convert(line, part) res = super().line_get_convert(line, part)
if line.get('asset_profile_id'): if line.get("asset_profile_id"):
# skip empty debit/credit # skip empty debit/credit
if res.get('debit') or res.get('credit'): if res.get("debit") or res.get("credit"):
res['asset_profile_id'] = line['asset_profile_id'] res["asset_profile_id"] = line["asset_profile_id"]
return res return res
@api.model @api.model
def inv_line_characteristic_hashcode(self, invoice_line): def inv_line_characteristic_hashcode(self, invoice_line):
res = super().inv_line_characteristic_hashcode( res = super().inv_line_characteristic_hashcode(invoice_line)
invoice_line) res += "-%s" % invoice_line.get("asset_profile_id", "False")
res += '-%s' % invoice_line.get('asset_profile_id', 'False')
return res return res
@api.model @api.model
def invoice_line_move_line_get(self): def invoice_line_move_line_get(self):
res = super().invoice_line_move_line_get() res = super().invoice_line_move_line_get()
invoice_line_obj = self.env['account.invoice.line'] invoice_line_obj = self.env["account.invoice.line"]
for vals in res: for vals in res:
if vals.get('invl_id'): if vals.get("invl_id"):
invline = invoice_line_obj.browse(vals['invl_id']) invline = invoice_line_obj.browse(vals["invl_id"])
if invline.asset_profile_id: if invline.asset_profile_id:
vals['asset_profile_id'] = invline.asset_profile_id.id vals["asset_profile_id"] = invline.asset_profile_id.id
return res return res
class AccountInvoiceLine(models.Model): class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line' _inherit = "account.invoice.line"
asset_profile_id = fields.Many2one( asset_profile_id = fields.Many2one(
comodel_name='account.asset.profile', comodel_name="account.asset.profile", string="Asset Profile"
string='Asset Profile') )
asset_id = fields.Many2one( asset_id = fields.Many2one(
comodel_name='account.asset', comodel_name="account.asset",
string='Asset', string="Asset",
domain=[('state', 'in', ['open', 'close'])], domain=[("state", "in", ["open", "close"])],
help="Complete this field when selling an asset " help="Complete this field when selling an asset "
"in order to facilitate the creation of the " "in order to facilitate the creation of the "
"asset removal accounting entries via the " "asset removal accounting entries via the "
"asset 'Removal' button") "asset 'Removal' button",
)
@api.onchange('account_id') @api.onchange("account_id")
def _onchange_account_id(self): def _onchange_account_id(self):
self.asset_profile_id = self.account_id.asset_profile_id.id self.asset_profile_id = self.account_id.asset_profile_id.id
return super()._onchange_account_id() return super()._onchange_account_id()

View File

@ -3,123 +3,136 @@
import logging import logging
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
# List of move's fields that can't be modified if move is linked # List of move's fields that can't be modified if move is linked
# with a depreciation line # with a depreciation line
FIELDS_AFFECTS_ASSET_MOVE = set(['journal_id', 'date']) FIELDS_AFFECTS_ASSET_MOVE = {"journal_id", "date"}
# List of move line's fields that can't be modified if move is linked # List of move line's fields that can't be modified if move is linked
# with a depreciation line # with a depreciation line
FIELDS_AFFECTS_ASSET_MOVE_LINE = \ FIELDS_AFFECTS_ASSET_MOVE_LINE = {
set(['credit', 'debit', 'account_id', 'journal_id', 'date', "credit",
'asset_profile_id', 'asset_id']) "debit",
"account_id",
"journal_id",
"date",
"asset_profile_id",
"asset_id",
}
class AccountMove(models.Model): class AccountMove(models.Model):
_inherit = 'account.move' _inherit = "account.move"
@api.multi @api.multi
def unlink(self): def unlink(self):
# for move in self: # for move in self:
deprs = self.env['account.asset.line'].search( deprs = self.env["account.asset.line"].search(
[('move_id', 'in', self.ids), [("move_id", "in", self.ids), ("type", "in", ["depreciate", "remove"])]
('type', 'in', ['depreciate', 'remove'])]) )
if deprs and not self.env.context.get('unlink_from_asset'): if deprs and not self.env.context.get("unlink_from_asset"):
raise UserError( raise UserError(
_("You are not allowed to remove an accounting entry " _(
"linked to an asset." "You are not allowed to remove an accounting entry "
"\nYou should remove such entries from the asset.")) "linked to an asset."
"\nYou should remove such entries from the asset."
)
)
# trigger store function # trigger store function
deprs.write({'move_id': False}) deprs.write({"move_id": False})
return super().unlink() return super().unlink()
@api.multi @api.multi
def write(self, vals): def write(self, vals):
if set(vals).intersection(FIELDS_AFFECTS_ASSET_MOVE): if set(vals).intersection(FIELDS_AFFECTS_ASSET_MOVE):
deprs = self.env['account.asset.line'].search( deprs = self.env["account.asset.line"].search(
[('move_id', 'in', self.ids), ('type', '=', 'depreciate')]) [("move_id", "in", self.ids), ("type", "=", "depreciate")]
)
if deprs: if deprs:
raise UserError( raise UserError(
_("You cannot change an accounting entry " _(
"linked to an asset depreciation line.")) "You cannot change an accounting entry "
"linked to an asset depreciation line."
)
)
return super().write(vals) return super().write(vals)
class AccountMoveLine(models.Model): class AccountMoveLine(models.Model):
_inherit = 'account.move.line' _inherit = "account.move.line"
asset_profile_id = fields.Many2one( asset_profile_id = fields.Many2one(
comodel_name='account.asset.profile', comodel_name="account.asset.profile", string="Asset Profile"
string='Asset Profile') )
asset_id = fields.Many2one( asset_id = fields.Many2one(
comodel_name='account.asset', comodel_name="account.asset", string="Asset", ondelete="restrict"
string='Asset', ondelete='restrict') )
@api.onchange('account_id') @api.onchange("account_id")
def _onchange_account_id(self): def _onchange_account_id(self):
self.asset_profile_id = self.account_id.asset_profile_id self.asset_profile_id = self.account_id.asset_profile_id
@api.model @api.model
def create(self, vals): def create(self, vals):
if vals.get('asset_id') and not self.env.context.get('allow_asset'): if vals.get("asset_id") and not self.env.context.get("allow_asset"):
raise UserError( raise UserError(
_("You are not allowed to link " _(
"an accounting entry to an asset." "You are not allowed to link "
"\nYou should generate such entries from the asset.")) "an accounting entry to an asset."
if vals.get('asset_profile_id'): "\nYou should generate such entries from the asset."
)
)
if vals.get("asset_profile_id"):
# create asset # create asset
asset_obj = self.env['account.asset'] asset_obj = self.env["account.asset"]
move = self.env['account.move'].browse(vals['move_id']) move = self.env["account.move"].browse(vals["move_id"])
depreciation_base = vals['debit'] or -vals['credit'] depreciation_base = vals["debit"] or -vals["credit"]
temp_vals = { temp_vals = {
'name': vals['name'], "name": vals["name"],
'profile_id': vals['asset_profile_id'], "profile_id": vals["asset_profile_id"],
'purchase_value': depreciation_base, "purchase_value": depreciation_base,
'partner_id': vals['partner_id'], "partner_id": vals["partner_id"],
'date_start': move.date, "date_start": move.date,
} }
if self.env.context.get('company_id'): if self.env.context.get("company_id"):
temp_vals['company_id'] = self.env.context['company_id'] temp_vals["company_id"] = self.env.context["company_id"]
temp_asset = asset_obj.new(temp_vals) temp_asset = asset_obj.new(temp_vals)
temp_asset._onchange_profile_id() temp_asset._onchange_profile_id()
asset_vals = temp_asset._convert_to_write(temp_asset._cache) asset_vals = temp_asset._convert_to_write(temp_asset._cache)
self._get_asset_analytic_values(vals, asset_vals) self._get_asset_analytic_values(vals, asset_vals)
asset = asset_obj.with_context( asset = asset_obj.with_context(
create_asset_from_move_line=True, create_asset_from_move_line=True, move_id=vals["move_id"]
move_id=vals['move_id']).create(asset_vals) ).create(asset_vals)
vals['asset_id'] = asset.id vals["asset_id"] = asset.id
return super().create(vals) return super().create(vals)
@api.multi @api.multi
def _prepare_asset_create(self, vals): def _prepare_asset_create(self, vals):
self.ensure_one() self.ensure_one()
debit = 'debit' in vals and vals.get('debit', 0.0) or self.debit debit = "debit" in vals and vals.get("debit", 0.0) or self.debit
credit = 'credit' in vals and \ credit = "credit" in vals and vals.get("credit", 0.0) or self.credit
vals.get('credit', 0.0) or self.credit
depreciation_base = debit - credit depreciation_base = debit - credit
partner_id = 'partner' in vals and \ partner_id = (
vals.get('partner', False) or self.partner_id.id "partner" in vals and vals.get("partner", False) or self.partner_id.id
date_start = 'date' in vals and \ )
vals.get('date', False) or self.date date_start = "date" in vals and vals.get("date", False) or self.date
return { return {
'name': vals.get('name') or self.name, "name": vals.get("name") or self.name,
'profile_id': vals['asset_profile_id'], "profile_id": vals["asset_profile_id"],
'purchase_value': depreciation_base, "purchase_value": depreciation_base,
'partner_id': partner_id, "partner_id": partner_id,
'date_start': date_start, "date_start": date_start,
'company_id': vals.get('company_id') or self.company_id.id, "company_id": vals.get("company_id") or self.company_id.id,
} }
@api.multi @api.multi
def write(self, vals): def write(self, vals):
if ( if set(vals).intersection(FIELDS_AFFECTS_ASSET_MOVE_LINE) and not (
set(vals).intersection(FIELDS_AFFECTS_ASSET_MOVE_LINE) and self.env.context.get("allow_asset_removal")
not ( and list(vals.keys()) == ["asset_id"]
self.env.context.get('allow_asset_removal') and
list(vals.keys()) == ['asset_id'])
): ):
# Check if at least one asset is linked to a move # Check if at least one asset is linked to a move
linked_asset = False linked_asset = False
@ -127,43 +140,49 @@ class AccountMoveLine(models.Model):
linked_asset = move.asset_id linked_asset = move.asset_id
if linked_asset: if linked_asset:
raise UserError( raise UserError(
_("You cannot change an accounting item " _(
"linked to an asset depreciation line.")) "You cannot change an accounting item "
if vals.get('asset_id'): "linked to an asset depreciation line."
)
)
if vals.get("asset_id"):
raise UserError( raise UserError(
_("You are not allowed to link " _(
"an accounting entry to an asset." "You are not allowed to link "
"\nYou should generate such entries from the asset.")) "an accounting entry to an asset."
if vals.get('asset_profile_id'): "\nYou should generate such entries from the asset."
)
)
if vals.get("asset_profile_id"):
if len(self) == 1: if len(self) == 1:
raise AssertionError(_( raise AssertionError(
'This option should only be used for a single id at a ' _("This option should only be used for a single id at a " "time.")
'time.')) )
asset_obj = self.env['account.asset'] asset_obj = self.env["account.asset"]
for aml in self: for aml in self:
if vals['asset_profile_id'] == aml.asset_profile_id.id: if vals["asset_profile_id"] == aml.asset_profile_id.id:
continue continue
# create asset # create asset
asset_vals = aml._prepare_asset_create(vals) asset_vals = aml._prepare_asset_create(vals)
self._play_onchange_profile_id(asset_vals) self._play_onchange_profile_id(asset_vals)
self._get_asset_analytic_values(vals, asset_vals) self._get_asset_analytic_values(vals, asset_vals)
asset = asset_obj.with_context( asset = asset_obj.with_context(
create_asset_from_move_line=True, create_asset_from_move_line=True, move_id=aml.move_id.id
move_id=aml.move_id.id).create(asset_vals) ).create(asset_vals)
vals['asset_id'] = asset.id vals["asset_id"] = asset.id
return super().write(vals) return super().write(vals)
@api.model @api.model
def _get_asset_analytic_values(self, vals, asset_vals): def _get_asset_analytic_values(self, vals, asset_vals):
asset_vals['account_analytic_id'] = vals.get( asset_vals["account_analytic_id"] = vals.get("analytic_account_id", False)
'analytic_account_id', False)
@api.model @api.model
def _play_onchange_profile_id(self, vals): def _play_onchange_profile_id(self, vals):
asset_obj = self.env['account.asset'] asset_obj = self.env["account.asset"]
asset_temp = asset_obj.new(vals) asset_temp = asset_obj.new(vals)
asset_temp._onchange_profile_id() asset_temp._onchange_profile_id()
for field in asset_temp._fields: for field in asset_temp._fields:
if field not in vals and asset_temp[field]: if field not in vals and asset_temp[field]:
vals[field] = asset_temp._fields[field].\ vals[field] = asset_temp._fields[field].convert_to_write(
convert_to_write(asset_temp[field], asset_temp) asset_temp[field], asset_temp
)

View File

@ -5,12 +5,13 @@ from odoo import fields, models
class Config(models.TransientModel): class Config(models.TransientModel):
_inherit = 'res.config.settings' _inherit = "res.config.settings"
module_account_asset_management = fields.Boolean( module_account_asset_management = fields.Boolean(
string='Assets management (OCA)', string="Assets management (OCA)",
help="""This allows you to manage the assets owned by a company help="""This allows you to manage the assets owned by a company
or a person. It keeps track of the depreciation occurred or a person. It keeps track of the depreciation occurred
on those assets, and creates account move for those on those assets, and creates account move for those
depreciation lines. depreciation lines.
This installs the module account_asset_management.""") This installs the module account_asset_management.""",
)

View File

@ -3,128 +3,154 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import calendar import calendar
from datetime import date, datetime
import time import time
from datetime import date, datetime
from odoo.tests.common import SavepointCase
from odoo import tools from odoo import tools
from odoo.modules.module import get_resource_path from odoo.modules.module import get_resource_path
from odoo.tests.common import SavepointCase
class TestAssetManagement(SavepointCase): class TestAssetManagement(SavepointCase):
@classmethod @classmethod
def _load(cls, module, *args): def _load(cls, module, *args):
tools.convert_file(cls.cr, module, tools.convert_file(
get_resource_path(module, *args), cls.cr,
{}, 'init', False, 'test', module,
cls.registry._assertion_report) get_resource_path(module, *args),
{},
"init",
False,
"test",
cls.registry._assertion_report,
)
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
cls._load('account', 'test', 'account_minimal_test.xml') cls._load("account", "test", "account_minimal_test.xml")
cls._load('account_asset_management', 'tests', cls._load("account_asset_management", "tests", "account_asset_test_data.xml")
'account_asset_test_data.xml')
# ENVIRONEMENTS # ENVIRONEMENTS
cls.asset_model = cls.env['account.asset'] cls.asset_model = cls.env["account.asset"]
cls.dl_model = cls.env['account.asset.line'] cls.dl_model = cls.env["account.asset.line"]
cls.remove_model = cls.env['account.asset.remove'] cls.remove_model = cls.env["account.asset.remove"]
cls.account_invoice = cls.env['account.invoice'] cls.account_invoice = cls.env["account.invoice"]
cls.account_move_line = cls.env['account.move.line'] cls.account_move_line = cls.env["account.move.line"]
cls.account_account = cls.env['account.account'] cls.account_account = cls.env["account.account"]
cls.account_journal = cls.env['account.journal'] cls.account_journal = cls.env["account.journal"]
cls.account_invoice_line = cls.env['account.invoice.line'] cls.account_invoice_line = cls.env["account.invoice.line"]
# INSTANCES # INSTANCES
# Instance: company # Instance: company
cls.company = cls.env.ref('base.main_company') cls.company = cls.env.ref("base.main_company")
# Instance: account type (receivable) # Instance: account type (receivable)
cls.type_recv = cls.env.ref('account.data_account_type_receivable') cls.type_recv = cls.env.ref("account.data_account_type_receivable")
# Instance: account type (payable) # Instance: account type (payable)
cls.type_payable = cls.env.ref('account.data_account_type_payable') cls.type_payable = cls.env.ref("account.data_account_type_payable")
# Instance: account (receivable) # Instance: account (receivable)
cls.account_recv = cls.account_account.create({ cls.account_recv = cls.account_account.create(
'name': 'test_account_receivable', {
'code': '123', "name": "test_account_receivable",
'user_type_id': cls.type_recv.id, "code": "123",
'company_id': cls.company.id, "user_type_id": cls.type_recv.id,
'reconcile': True}) "company_id": cls.company.id,
"reconcile": True,
}
)
# Instance: account (payable) # Instance: account (payable)
cls.account_payable = cls.account_account.create({ cls.account_payable = cls.account_account.create(
'name': 'test_account_payable', {
'code': '321', "name": "test_account_payable",
'user_type_id': cls.type_payable.id, "code": "321",
'company_id': cls.company.id, "user_type_id": cls.type_payable.id,
'reconcile': True}) "company_id": cls.company.id,
"reconcile": True,
}
)
# Instance: partner # Instance: partner
cls.partner = cls.env.ref('base.res_partner_2') cls.partner = cls.env.ref("base.res_partner_2")
# Instance: journal # Instance: journal
cls.journal = cls.account_journal.search( cls.journal = cls.account_journal.search([("type", "=", "purchase")])[0]
[('type', '=', 'purchase')])[0]
# Instance: product # Instance: product
cls.product = cls.env.ref('product.product_product_4') cls.product = cls.env.ref("product.product_product_4")
# Instance: invoice line # Instance: invoice line
cls.invoice_line = cls.account_invoice_line.create({ cls.invoice_line = cls.account_invoice_line.create(
'name': 'test', {
'account_id': cls.account_payable.id, "name": "test",
'price_unit': 2000.00, "account_id": cls.account_payable.id,
'quantity': 1, "price_unit": 2000.00,
'product_id': cls.product.id}) "quantity": 1,
"product_id": cls.product.id,
}
)
# Instance: invoice # Instance: invoice
cls.invoice = cls.account_invoice.create({ cls.invoice = cls.account_invoice.create(
'partner_id': cls.partner.id, {
'account_id': cls.account_recv.id, "partner_id": cls.partner.id,
'journal_id': cls.journal.id, "account_id": cls.account_recv.id,
'invoice_line_ids': [(4, cls.invoice_line.id)]}) "journal_id": cls.journal.id,
"invoice_line_ids": [(4, cls.invoice_line.id)],
}
)
cls.invoice_line_2 = cls.account_invoice_line.create({ cls.invoice_line_2 = cls.account_invoice_line.create(
'name': 'test 2', {
'account_id': cls.account_payable.id, "name": "test 2",
'price_unit': 10000.00, "account_id": cls.account_payable.id,
'quantity': 1, "price_unit": 10000.00,
'product_id': cls.product.id}) "quantity": 1,
cls.invoice_line_3 = cls.account_invoice_line.create({ "product_id": cls.product.id,
'name': 'test 3', }
'account_id': cls.account_payable.id, )
'price_unit': 20000.00, cls.invoice_line_3 = cls.account_invoice_line.create(
'quantity': 1, {
'product_id': cls.product.id}) "name": "test 3",
"account_id": cls.account_payable.id,
"price_unit": 20000.00,
"quantity": 1,
"product_id": cls.product.id,
}
)
cls.invoice_2 = cls.account_invoice.create({ cls.invoice_2 = cls.account_invoice.create(
'partner_id': cls.partner.id, {
'account_id': cls.account_recv.id, "partner_id": cls.partner.id,
'journal_id': cls.journal.id, "account_id": cls.account_recv.id,
'invoice_line_ids': [(4, cls.invoice_line_2.id), "journal_id": cls.journal.id,
(4, cls.invoice_line_3.id)]}) "invoice_line_ids": [
(4, cls.invoice_line_2.id),
(4, cls.invoice_line_3.id),
],
}
)
def test_01_nonprorata_basic(self): def test_01_nonprorata_basic(self):
"""Basic tests of depreciation board computations and postings.""" """Basic tests of depreciation board computations and postings."""
# #
# first load demo assets and do some sanity checks # first load demo assets and do some sanity checks
# #
ict0 = self.browse_ref('account_asset_management.' ict0 = self.browse_ref("account_asset_management." "account_asset_asset_ict0")
'account_asset_asset_ict0') self.assertEqual(ict0.state, "draft")
self.assertEqual(ict0.state, 'draft')
self.assertEqual(ict0.purchase_value, 1500) self.assertEqual(ict0.purchase_value, 1500)
self.assertEqual(ict0.salvage_value, 0) self.assertEqual(ict0.salvage_value, 0)
self.assertEqual(ict0.depreciation_base, 1500) self.assertEqual(ict0.depreciation_base, 1500)
self.assertEqual(len(ict0.depreciation_line_ids), 1) self.assertEqual(len(ict0.depreciation_line_ids), 1)
vehicle0 = self.browse_ref('account_asset_management.' vehicle0 = self.browse_ref(
'account_asset_asset_vehicle0') "account_asset_management." "account_asset_asset_vehicle0"
self.assertEqual(vehicle0.state, 'draft') )
self.assertEqual(vehicle0.state, "draft")
self.assertEqual(vehicle0.purchase_value, 12000) self.assertEqual(vehicle0.purchase_value, 12000)
self.assertEqual(vehicle0.salvage_value, 2000) self.assertEqual(vehicle0.salvage_value, 2000)
self.assertEqual(vehicle0.depreciation_base, 10000) self.assertEqual(vehicle0.depreciation_base, 10000)
@ -148,305 +174,320 @@ class TestAssetManagement(SavepointCase):
ict0.validate() ict0.validate()
ict0.depreciation_line_ids[1].create_move() ict0.depreciation_line_ids[1].create_move()
ict0.refresh() ict0.refresh()
self.assertEqual(ict0.state, 'open') self.assertEqual(ict0.state, "open")
self.assertEqual(ict0.value_depreciated, 500) self.assertEqual(ict0.value_depreciated, 500)
self.assertEqual(ict0.value_residual, 1000) self.assertEqual(ict0.value_residual, 1000)
vehicle0.validate() vehicle0.validate()
vehicle0.depreciation_line_ids[1].create_move() vehicle0.depreciation_line_ids[1].create_move()
vehicle0.refresh() vehicle0.refresh()
self.assertEqual(vehicle0.state, 'open') self.assertEqual(vehicle0.state, "open")
self.assertEqual(vehicle0.value_depreciated, 2000) self.assertEqual(vehicle0.value_depreciated, 2000)
self.assertEqual(vehicle0.value_residual, 8000) self.assertEqual(vehicle0.value_residual, 8000)
def test_02_prorata_basic(self): def test_02_prorata_basic(self):
"""Prorata temporis depreciation basic test.""" """Prorata temporis depreciation basic test."""
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 3333, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': time.strftime('%Y-07-07'), "purchase_value": 3333,
'method_time': 'year', "salvage_value": 0,
'method_number': 5, "date_start": time.strftime("%Y-07-07"),
'method_period': 'month', "method_time": "year",
'prorata': True, "method_number": 5,
}) "method_period": "month",
"prorata": True,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
if calendar.isleap(date.today().year): if calendar.isleap(date.today().year):
self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, self.assertAlmostEqual(
46.44, places=2) asset.depreciation_line_ids[1].amount, 46.44, places=2
)
else: else:
self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, self.assertAlmostEqual(
47.33, places=2) asset.depreciation_line_ids[1].amount, 47.33, places=2
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, )
55.55, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, 55.55, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, 55.55, places=2)
55.55, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[4].amount, 55.55, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[4].amount, self.assertAlmostEqual(asset.depreciation_line_ids[5].amount, 55.55, places=2)
55.55, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[6].amount, 55.55, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[5].amount,
55.55, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[6].amount,
55.55, places=2)
if calendar.isleap(date.today().year): if calendar.isleap(date.today().year):
self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, self.assertAlmostEqual(
9.11, places=2) asset.depreciation_line_ids[-1].amount, 9.11, places=2
)
else: else:
self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, self.assertAlmostEqual(
8.22, places=2) asset.depreciation_line_ids[-1].amount, 8.22, places=2
)
def test_03_proprata_init_prev_year(self): def test_03_proprata_init_prev_year(self):
"""Prorata temporis depreciation with init value in prev year.""" """Prorata temporis depreciation with init value in prev year."""
# I create an asset in current year # I create an asset in current year
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 3333, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': '%d-07-07' % (datetime.now().year - 1,), "purchase_value": 3333,
'method_time': 'year', "salvage_value": 0,
'method_number': 5, "date_start": "%d-07-07" % (datetime.now().year - 1,),
'method_period': 'month', "method_time": "year",
'prorata': True, "method_number": 5,
}) "method_period": "month",
"prorata": True,
}
)
# I create a initial depreciation line in previous year # I create a initial depreciation line in previous year
self.dl_model.create({ self.dl_model.create(
'asset_id': asset.id, {
'amount': 325.08, "asset_id": asset.id,
'line_date': '%d-12-31' % (datetime.now().year - 1,), "amount": 325.08,
'type': 'depreciate', "line_date": "%d-12-31" % (datetime.now().year - 1,),
'init_entry': True, "type": "depreciate",
}) "init_entry": True,
}
)
self.assertEqual(len(asset.depreciation_line_ids), 2) self.assertEqual(len(asset.depreciation_line_ids), 2)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
# I check the depreciated value is the initial value # I check the depreciated value is the initial value
self.assertAlmostEqual(asset.value_depreciated, 325.08, self.assertAlmostEqual(asset.value_depreciated, 325.08, places=2)
places=2)
# I check computed values in the depreciation board # I check computed values in the depreciation board
self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, 55.55, self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, 55.55, places=2)
places=2)
if calendar.isleap(date.today().year - 1): if calendar.isleap(date.today().year - 1):
# for leap years the first year depreciation amount of 325.08 # for leap years the first year depreciation amount of 325.08
# is too high and hence a correction is applied to the next # is too high and hence a correction is applied to the next
# entry of the table # entry of the table
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, self.assertAlmostEqual(
54.66, places=2) asset.depreciation_line_ids[2].amount, 54.66, places=2
self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, )
55.55, places=2) self.assertAlmostEqual(
self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, asset.depreciation_line_ids[3].amount, 55.55, places=2
9.11, places=2) )
self.assertAlmostEqual(
asset.depreciation_line_ids[-1].amount, 9.11, places=2
)
else: else:
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, self.assertAlmostEqual(
55.55, places=2) asset.depreciation_line_ids[2].amount, 55.55, places=2
self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, )
8.22, places=2) self.assertAlmostEqual(
asset.depreciation_line_ids[-1].amount, 8.22, places=2
)
def test_04_prorata_init_cur_year(self): def test_04_prorata_init_cur_year(self):
"""Prorata temporis depreciation with init value in curent year.""" """Prorata temporis depreciation with init value in curent year."""
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 3333, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': time.strftime('%Y-07-07'), "purchase_value": 3333,
'method_time': 'year', "salvage_value": 0,
'method_number': 5, "date_start": time.strftime("%Y-07-07"),
'method_period': 'month', "method_time": "year",
'prorata': True, "method_number": 5,
}) "method_period": "month",
self.dl_model.create({ "prorata": True,
'asset_id': asset.id, }
'amount': 279.44, )
'line_date': time.strftime('%Y-11-30'), self.dl_model.create(
'type': 'depreciate', {
'init_entry': True, "asset_id": asset.id,
}) "amount": 279.44,
"line_date": time.strftime("%Y-11-30"),
"type": "depreciate",
"init_entry": True,
}
)
self.assertEqual(len(asset.depreciation_line_ids), 2) self.assertEqual(len(asset.depreciation_line_ids), 2)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
# I check the depreciated value is the initial value # I check the depreciated value is the initial value
self.assertAlmostEqual(asset.value_depreciated, 279.44, self.assertAlmostEqual(asset.value_depreciated, 279.44, places=2)
places=2)
# I check computed values in the depreciation board # I check computed values in the depreciation board
if calendar.isleap(date.today().year): if calendar.isleap(date.today().year):
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, self.assertAlmostEqual(
44.75, places=2) asset.depreciation_line_ids[2].amount, 44.75, places=2
)
else: else:
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, self.assertAlmostEqual(
45.64, places=2) asset.depreciation_line_ids[2].amount, 45.64, places=2
self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, )
55.55, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, 55.55, places=2)
if calendar.isleap(date.today().year): if calendar.isleap(date.today().year):
self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, self.assertAlmostEqual(
9.11, places=2) asset.depreciation_line_ids[-1].amount, 9.11, places=2
)
else: else:
self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, self.assertAlmostEqual(
8.22, places=2) asset.depreciation_line_ids[-1].amount, 8.22, places=2
)
def test_05_degressive_linear(self): def test_05_degressive_linear(self):
"""Degressive-Linear with annual and quarterly depreciation.""" """Degressive-Linear with annual and quarterly depreciation."""
# annual depreciation # annual depreciation
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 1000, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': time.strftime('%Y-07-07'), "purchase_value": 1000,
'method_time': 'year', "salvage_value": 0,
'method': 'degr-linear', "date_start": time.strftime("%Y-07-07"),
'method_progress_factor': 0.40, "method_time": "year",
'method_number': 5, "method": "degr-linear",
'method_period': 'year', "method_progress_factor": 0.40,
'prorata': False, "method_number": 5,
}) "method_period": "year",
"prorata": False,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
# check values in the depreciation board # check values in the depreciation board
self.assertEqual(len(asset.depreciation_line_ids), 5) self.assertEqual(len(asset.depreciation_line_ids), 5)
self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, 400.00, places=2)
400.00, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, 240.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, 200.00, places=2)
240.00, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[4].amount, 160.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[3].amount,
200.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[4].amount,
160.00, places=2)
# quarterly depreciation # quarterly depreciation
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 1000, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': time.strftime('%Y-07-07'), "purchase_value": 1000,
'method_time': 'year', "salvage_value": 0,
'method': 'degr-linear', "date_start": time.strftime("%Y-07-07"),
'method_progress_factor': 0.40, "method_time": "year",
'method_number': 5, "method": "degr-linear",
'method_period': 'quarter', "method_progress_factor": 0.40,
'prorata': False, "method_number": 5,
}) "method_period": "quarter",
"prorata": False,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
# check values in the depreciation board # check values in the depreciation board
self.assertEqual(len(asset.depreciation_line_ids), 15) self.assertEqual(len(asset.depreciation_line_ids), 15)
# lines prior to asset start period are grouped in the first entry # lines prior to asset start period are grouped in the first entry
self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, 300.00, places=2)
300.00, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, 60.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, self.assertAlmostEqual(asset.depreciation_line_ids[7].amount, 50.00, places=2)
60.00, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[13].amount, 40.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[7].amount,
50.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[13].amount,
40.00, places=2)
def test_06_degressive_limit(self): def test_06_degressive_limit(self):
"""Degressive with annual depreciation.""" """Degressive with annual depreciation."""
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 1000, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 100, ),
'date_start': time.strftime('%Y-07-07'), "purchase_value": 1000,
'method_time': 'year', "salvage_value": 100,
'method': 'degr-limit', "date_start": time.strftime("%Y-07-07"),
'method_progress_factor': 0.40, "method_time": "year",
'method_number': 5, "method": "degr-limit",
'method_period': 'year', "method_progress_factor": 0.40,
'prorata': False, "method_number": 5,
}) "method_period": "year",
"prorata": False,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
# check values in the depreciation board # check values in the depreciation board
self.assertEqual(len(asset.depreciation_line_ids), 6) self.assertEqual(len(asset.depreciation_line_ids), 6)
self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, 400.00, places=2)
400.00, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, 240.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, 144.00, places=2)
240.00, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[4].amount, 86.40, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[3].amount, self.assertAlmostEqual(asset.depreciation_line_ids[5].amount, 29.60, places=2)
144.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[4].amount,
86.40, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[5].amount,
29.60, places=2)
def test_07_linear_limit(self): def test_07_linear_limit(self):
"""Degressive with annual depreciation.""" """Degressive with annual depreciation."""
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 1000, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 100, ),
'date_start': time.strftime('%Y-07-07'), "purchase_value": 1000,
'method_time': 'year', "salvage_value": 100,
'method': 'linear-limit', "date_start": time.strftime("%Y-07-07"),
'method_number': 5, "method_time": "year",
'method_period': 'year', "method": "linear-limit",
'prorata': False, "method_number": 5,
}) "method_period": "year",
"prorata": False,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
# check values in the depreciation board # check values in the depreciation board
self.assertEqual(len(asset.depreciation_line_ids), 6) self.assertEqual(len(asset.depreciation_line_ids), 6)
self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, 200.00, places=2)
200.00, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, 100.00, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount,
100.00, places=2)
def test_08_asset_removal(self): def test_08_asset_removal(self):
"""Asset removal""" """Asset removal"""
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset removal', {
'profile_id': self.ref('account_asset_management.' "name": "test asset removal",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 5000, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': '2019-01-01', "purchase_value": 5000,
'method_time': 'year', "salvage_value": 0,
'method_number': 5, "date_start": "2019-01-01",
'method_period': 'quarter', "method_time": "year",
'prorata': False, "method_number": 5,
}) "method_period": "quarter",
"prorata": False,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.validate() asset.validate()
wiz_ctx = { wiz_ctx = {"active_id": asset.id, "early_removal": True}
'active_id': asset.id, wiz = self.remove_model.with_context(wiz_ctx).create(
'early_removal': True, {
} "date_remove": "2019-01-31",
wiz = self.remove_model.with_context(wiz_ctx).create({ "sale_value": 0.0,
'date_remove': '2019-01-31', "posting_regime": "gain_loss_on_sale",
'sale_value': 0.0, "account_plus_value_id": self.ref("account.a_sale"),
'posting_regime': 'gain_loss_on_sale', "account_min_value_id": self.ref("account.a_expense"),
'account_plus_value_id': self.ref('account.a_sale'), }
'account_min_value_id': self.ref('account.a_expense'), )
})
wiz.remove() wiz.remove()
asset.refresh() asset.refresh()
self.assertEqual(len(asset.depreciation_line_ids), 3) self.assertEqual(len(asset.depreciation_line_ids), 3)
self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, self.assertAlmostEqual(asset.depreciation_line_ids[1].amount, 81.46, places=2)
81.46, places=2) self.assertAlmostEqual(asset.depreciation_line_ids[2].amount, 4918.54, places=2)
self.assertAlmostEqual(asset.depreciation_line_ids[2].amount,
4918.54, places=2)
def test_09_asset_from_invoice(self): def test_09_asset_from_invoice(self):
all_asset = self.env['account.asset'].search([]) all_asset = self.env["account.asset"].search([])
invoice = self.invoice invoice = self.invoice
asset_profile = self.env.ref( asset_profile = self.env.ref(
'account_asset_management.account_asset_profile_car_5Y') "account_asset_management.account_asset_profile_car_5Y"
)
asset_profile.asset_product_item = False asset_profile.asset_product_item = False
self.assertTrue(len(invoice.invoice_line_ids) > 0) self.assertTrue(len(invoice.invoice_line_ids) > 0)
line = invoice.invoice_line_ids[0] line = invoice.invoice_line_ids[0]
@ -455,21 +496,22 @@ class TestAssetManagement(SavepointCase):
line.asset_profile_id = asset_profile line.asset_profile_id = asset_profile
invoice.action_invoice_open() invoice.action_invoice_open()
# I get all asset after invoice validation # I get all asset after invoice validation
current_asset = self.env['account.asset'].search([]) current_asset = self.env["account.asset"].search([])
# I get the new asset # I get the new asset
new_asset = current_asset - all_asset new_asset = current_asset - all_asset
# I check that a new asset is created # I check that a new asset is created
self.assertEqual(len(new_asset), 1) self.assertEqual(len(new_asset), 1)
# I check that the new asset has the correct purchase value # I check that the new asset has the correct purchase value
self.assertAlmostEqual(new_asset.purchase_value, self.assertAlmostEqual(
-line.price_unit * line.quantity, new_asset.purchase_value, -line.price_unit * line.quantity, places=2
places=2) )
def test_10_asset_from_invoice_product_item(self): def test_10_asset_from_invoice_product_item(self):
all_asset = self.env['account.asset'].search([]) all_asset = self.env["account.asset"].search([])
invoice = self.invoice invoice = self.invoice
asset_profile = self.env.ref( asset_profile = self.env.ref(
'account_asset_management.account_asset_profile_car_5Y') "account_asset_management.account_asset_profile_car_5Y"
)
asset_profile.asset_product_item = True asset_profile.asset_product_item = True
self.assertTrue(len(invoice.invoice_line_ids) > 0) self.assertTrue(len(invoice.invoice_line_ids) > 0)
line = invoice.invoice_line_ids[0] line = invoice.invoice_line_ids[0]
@ -478,123 +520,134 @@ class TestAssetManagement(SavepointCase):
line.asset_profile_id = asset_profile line.asset_profile_id = asset_profile
invoice.action_invoice_open() invoice.action_invoice_open()
# I get all asset after invoice validation # I get all asset after invoice validation
current_asset = self.env['account.asset'].search([]) current_asset = self.env["account.asset"].search([])
# I get the new asset # I get the new asset
new_asset = current_asset - all_asset new_asset = current_asset - all_asset
# I check that a new asset is created # I check that a new asset is created
self.assertEqual(len(new_asset), line.quantity) self.assertEqual(len(new_asset), line.quantity)
for asset in new_asset: for asset in new_asset:
# I check that the new asset has the correct purchase value # I check that the new asset has the correct purchase value
self.assertAlmostEqual( self.assertAlmostEqual(asset.purchase_value, -line.price_unit, places=2)
asset.purchase_value, -line.price_unit, places=2)
def test_11_assets_from_invoice(self): def test_11_assets_from_invoice(self):
all_assets = self.env['account.asset'].search([]) all_assets = self.env["account.asset"].search([])
invoice = self.invoice_2 invoice = self.invoice_2
asset_profile = self.env.ref( asset_profile = self.env.ref(
'account_asset_management.account_asset_profile_car_5Y') "account_asset_management.account_asset_profile_car_5Y"
)
asset_profile.asset_product_item = True asset_profile.asset_product_item = True
# Compute depreciation lines on invoice validation # Compute depreciation lines on invoice validation
asset_profile.open_asset = True asset_profile.open_asset = True
self.assertTrue(len(invoice.invoice_line_ids) == 2) self.assertTrue(len(invoice.invoice_line_ids) == 2)
invoice.invoice_line_ids.write({ invoice.invoice_line_ids.write(
'quantity': 1, {"quantity": 1, "asset_profile_id": asset_profile.id}
'asset_profile_id': asset_profile.id, )
})
invoice.action_invoice_open() invoice.action_invoice_open()
# Retrieve all assets after invoice validation # Retrieve all assets after invoice validation
current_assets = self.env['account.asset'].search([]) current_assets = self.env["account.asset"].search([])
# What are the new assets? # What are the new assets?
new_assets = current_assets - all_assets new_assets = current_assets - all_assets
self.assertEqual(len(new_assets), 2) self.assertEqual(len(new_assets), 2)
for asset in new_assets: for asset in new_assets:
dlines = asset.depreciation_line_ids.filtered( dlines = asset.depreciation_line_ids.filtered(
lambda l: l.type == 'depreciate') lambda l: l.type == "depreciate"
)
dlines = dlines.sorted(key=lambda l: l.line_date) dlines = dlines.sorted(key=lambda l: l.line_date)
self.assertAlmostEqual(dlines[0].depreciated_value, 0.0) self.assertAlmostEqual(dlines[0].depreciated_value, 0.0)
self.assertAlmostEqual(dlines[-1].remaining_value, 0.0) self.assertAlmostEqual(dlines[-1].remaining_value, 0.0)
def test_12_prorata_days_calc(self): def test_12_prorata_days_calc(self):
"""Prorata temporis depreciation with days calc option.""" """Prorata temporis depreciation with days calc option."""
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 3333, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': '2019-07-07', "purchase_value": 3333,
'method_time': 'year', "salvage_value": 0,
'method_number': 5, "date_start": "2019-07-07",
'method_period': 'month', "method_time": "year",
'prorata': True, "method_number": 5,
'days_calc': True, "method_period": "month",
'use_leap_years': False, "prorata": True,
}) "days_calc": True,
"use_leap_years": False,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
day_rate = 3333 / 1827 # 3333 / 1827 depreciation days day_rate = 3333 / 1827 # 3333 / 1827 depreciation days
for i in range(1, 10): for i in range(1, 10):
self.assertAlmostEqual( self.assertAlmostEqual(
asset.depreciation_line_ids[i].amount, asset.depreciation_line_ids[i].amount,
asset.depreciation_line_ids[i].line_days * day_rate, places=2) asset.depreciation_line_ids[i].line_days * day_rate,
places=2,
)
# Last depreciation remaining # Last depreciation remaining
self.assertAlmostEqual( self.assertAlmostEqual(asset.depreciation_line_ids[-1].amount, 11.05, places=2)
asset.depreciation_line_ids[-1].amount, 11.05, places=2)
def test_13_use_leap_year(self): def test_13_use_leap_year(self):
# When you use the depreciation with years method and using lap years, # When you use the depreciation with years method and using lap years,
# the depreciation amount is calculated as 10000 / 1826 days * 365 days # the depreciation amount is calculated as 10000 / 1826 days * 365 days
# = yearly depreciation amount of 1998.90. # = yearly depreciation amount of 1998.90.
# Then 1998.90 / 12 = 166.58 # Then 1998.90 / 12 = 166.58
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 10000, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': time.strftime('2019-01-01'), "purchase_value": 10000,
'method_time': 'year', "salvage_value": 0,
'method_number': 5, "date_start": time.strftime("2019-01-01"),
'method_period': 'month', "method_time": "year",
'prorata': False, "method_number": 5,
'days_calc': False, "method_period": "month",
'use_leap_years': True, "prorata": False,
}) "days_calc": False,
"use_leap_years": True,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
for i in range(2, 11): for i in range(2, 11):
self.assertAlmostEqual( self.assertAlmostEqual(
asset.depreciation_line_ids[i].amount, 166.58, places=2) asset.depreciation_line_ids[i].amount, 166.58, places=2
)
self.assertAlmostEqual( self.assertAlmostEqual(
asset.depreciation_line_ids[13].depreciated_value, 1998.90, asset.depreciation_line_ids[13].depreciated_value, 1998.90, places=2
places=2) )
def test_14_not_use_leap_year(self): def test_14_not_use_leap_year(self):
# When you run a depreciation with method = 'year' and no not use # When you run a depreciation with method = 'year' and no not use
# lap years you divide 1000 / 5 years = 2000, then divided by 12 months # lap years you divide 1000 / 5 years = 2000, then divided by 12 months
# to get 166.67 per month, equal for all periods. # to get 166.67 per month, equal for all periods.
asset = self.asset_model.create({ asset = self.asset_model.create(
'name': 'test asset', {
'profile_id': self.ref('account_asset_management.' "name": "test asset",
'account_asset_profile_car_5Y'), "profile_id": self.ref(
'purchase_value': 10000, "account_asset_management." "account_asset_profile_car_5Y"
'salvage_value': 0, ),
'date_start': time.strftime('2019-01-01'), "purchase_value": 10000,
'method_time': 'year', "salvage_value": 0,
'method_number': 5, "date_start": time.strftime("2019-01-01"),
'method_period': 'month', "method_time": "year",
'prorata': False, "method_number": 5,
'days_calc': False, "method_period": "month",
'use_leap_years': False, "prorata": False,
}) "days_calc": False,
"use_leap_years": False,
}
)
asset.compute_depreciation_board() asset.compute_depreciation_board()
asset.refresh() asset.refresh()
for i in range(1, 11): for i in range(1, 11):
self.assertAlmostEqual( self.assertAlmostEqual(
asset.depreciation_line_ids[1].amount, 166.67, places=2) asset.depreciation_line_ids[1].amount, 166.67, places=2
)
# In the last month of the fiscal year we compensate for the small # In the last month of the fiscal year we compensate for the small
# deviations if that is necessary. # deviations if that is necessary.
self.assertAlmostEqual( self.assertAlmostEqual(asset.depreciation_line_ids[12].amount, 166.63, places=2)
asset.depreciation_line_ids[12].amount, 166.63, places=2)

View File

@ -1,65 +1,65 @@
# Copyright 2009-2018 Noviat # Copyright 2009-2018 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
class AccountAssetCompute(models.TransientModel): class AccountAssetCompute(models.TransientModel):
_name = 'account.asset.compute' _name = "account.asset.compute"
_description = "Compute Assets" _description = "Compute Assets"
date_end = fields.Date( date_end = fields.Date(
string='Date', required=True, string="Date",
required=True,
default=fields.Date.today, default=fields.Date.today,
help="All depreciation lines prior to this date will be automatically" help="All depreciation lines prior to this date will be automatically"
" posted") " posted",
)
note = fields.Text() note = fields.Text()
@api.multi @api.multi
def asset_compute(self): def asset_compute(self):
assets = self.env['account.asset'].search( assets = self.env["account.asset"].search([("state", "=", "open")])
[('state', '=', 'open')])
created_move_ids, error_log = assets._compute_entries( created_move_ids, error_log = assets._compute_entries(
self.date_end, check_triggers=True) self.date_end, check_triggers=True
)
if error_log: if error_log:
module = __name__.split('addons.')[1].split('.')[0] module = __name__.split("addons.")[1].split(".")[0]
result_view = self.env.ref( result_view = self.env.ref("{}.{}_view_form_result".format(module, self._table))
'%s.%s_view_form_result' self.note = _("Compute Assets errors") + ":\n" + error_log
% (module, self._table))
self.note = _("Compute Assets errors") + ':\n' + error_log
return { return {
'name': _('Compute Assets result'), "name": _("Compute Assets result"),
'res_id': self.id, "res_id": self.id,
'view_type': 'form', "view_type": "form",
'view_mode': 'form', "view_mode": "form",
'res_model': 'account.asset.compute', "res_model": "account.asset.compute",
'view_id': result_view.id, "view_id": result_view.id,
'target': 'new', "target": "new",
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'context': {'asset_move_ids': created_move_ids}, "context": {"asset_move_ids": created_move_ids},
} }
return { return {
'name': _('Created Asset Moves'), "name": _("Created Asset Moves"),
'view_type': 'form', "view_type": "form",
'view_mode': 'tree,form', "view_mode": "tree,form",
'res_model': 'account.move', "res_model": "account.move",
'view_id': False, "view_id": False,
'domain': [('id', 'in', created_move_ids)], "domain": [("id", "in", created_move_ids)],
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
} }
@api.multi @api.multi
def view_asset_moves(self): def view_asset_moves(self):
self.ensure_one() self.ensure_one()
domain = [('id', 'in', self.env.context.get('asset_move_ids', []))] domain = [("id", "in", self.env.context.get("asset_move_ids", []))]
return { return {
'name': _('Created Asset Moves'), "name": _("Created Asset Moves"),
'view_type': 'form', "view_type": "form",
'view_mode': 'tree,form', "view_mode": "tree,form",
'res_model': 'account.move', "res_model": "account.move",
'view_id': False, "view_id": False,
'domain': domain, "domain": domain,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
} }

View File

@ -1,159 +1,170 @@
# Copyright 2009-2018 Noviat # Copyright 2009-2018 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from dateutil.relativedelta import relativedelta
import logging import logging
from odoo import api, fields, models, _ from dateutil.relativedelta import relativedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class AccountAssetRemove(models.TransientModel): class AccountAssetRemove(models.TransientModel):
_name = 'account.asset.remove' _name = "account.asset.remove"
_description = 'Remove Asset' _description = "Remove Asset"
date_remove = fields.Date( date_remove = fields.Date(
string='Asset Removal Date', required=True, string="Asset Removal Date",
required=True,
default=fields.Date.today, default=fields.Date.today,
help="Removal date must be after the last posted entry " help="Removal date must be after the last posted entry "
"in case of early removal") "in case of early removal",
force_date = fields.Date( )
string='Force accounting date') force_date = fields.Date(string="Force accounting date")
sale_value = fields.Float( sale_value = fields.Float(
string='Sale Value', string="Sale Value", default=lambda self: self._default_sale_value()
default=lambda self: self._default_sale_value()) )
account_sale_id = fields.Many2one( account_sale_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
string='Asset Sale Account', string="Asset Sale Account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
default=lambda self: self._default_account_sale_id()) default=lambda self: self._default_account_sale_id(),
)
account_plus_value_id = fields.Many2one( account_plus_value_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
string='Plus-Value Account', string="Plus-Value Account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
default=lambda self: self._default_account_plus_value_id()) default=lambda self: self._default_account_plus_value_id(),
)
account_min_value_id = fields.Many2one( account_min_value_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
string='Min-Value Account', string="Min-Value Account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
default=lambda self: self._default_account_min_value_id()) default=lambda self: self._default_account_min_value_id(),
)
account_residual_value_id = fields.Many2one( account_residual_value_id = fields.Many2one(
comodel_name='account.account', comodel_name="account.account",
string='Residual Value Account', string="Residual Value Account",
domain=[('deprecated', '=', False)], domain=[("deprecated", "=", False)],
default=lambda self: self._default_account_residual_value_id()) default=lambda self: self._default_account_residual_value_id(),
)
posting_regime = fields.Selection( posting_regime = fields.Selection(
selection=lambda self: self._selection_posting_regime(), selection=lambda self: self._selection_posting_regime(),
string='Removal Entry Policy', string="Removal Entry Policy",
required=True, required=True,
default=lambda self: self._get_posting_regime(), default=lambda self: self._get_posting_regime(),
help="Removal Entry Policy \n" help="Removal Entry Policy \n"
" * Residual Value: The non-depreciated value will be " " * Residual Value: The non-depreciated value will be "
"posted on the 'Residual Value Account' \n" "posted on the 'Residual Value Account' \n"
" * Gain/Loss on Sale: The Gain or Loss will be posted on " " * Gain/Loss on Sale: The Gain or Loss will be posted on "
"the 'Plus-Value Account' or 'Min-Value Account' ") "the 'Plus-Value Account' or 'Min-Value Account' ",
note = fields.Text('Notes') )
note = fields.Text("Notes")
@api.constrains('sale_value') @api.constrains("sale_value")
def _check_sale_value(self): def _check_sale_value(self):
if self.sale_value < 0: if self.sale_value < 0:
raise ValidationError(_('The Sale Value must be positive!')) raise ValidationError(_("The Sale Value must be positive!"))
@api.model @api.model
def _default_sale_value(self): def _default_sale_value(self):
return self._get_sale()['sale_value'] return self._get_sale()["sale_value"]
@api.model @api.model
def _default_account_sale_id(self): def _default_account_sale_id(self):
return self._get_sale()['account_sale_id'] return self._get_sale()["account_sale_id"]
def _get_sale(self): def _get_sale(self):
asset_id = self.env.context.get('active_id') asset_id = self.env.context.get("active_id")
sale_value = 0.0 sale_value = 0.0
account_sale_id = False account_sale_id = False
inv_lines = self.env['account.invoice.line'].search( inv_lines = self.env["account.invoice.line"].search(
[('asset_id', '=', asset_id)]) [("asset_id", "=", asset_id)]
)
for line in inv_lines: for line in inv_lines:
inv = line.invoice_id inv = line.invoice_id
comp_curr = inv.company_id.currency_id comp_curr = inv.company_id.currency_id
inv_curr = inv.currency_id inv_curr = inv.currency_id
if line.invoice_id.state in ['open', 'paid']: if line.invoice_id.state in ["open", "paid"]:
account_sale_id = line.account_id.id account_sale_id = line.account_id.id
amount = line.price_subtotal amount = line.price_subtotal
if inv_curr != comp_curr: if inv_curr != comp_curr:
amount = comp_curr.compute(amount) amount = comp_curr.compute(amount)
sale_value += amount sale_value += amount
return {'sale_value': sale_value, 'account_sale_id': account_sale_id} return {"sale_value": sale_value, "account_sale_id": account_sale_id}
@api.model @api.model
def _default_account_plus_value_id(self): def _default_account_plus_value_id(self):
asset_id = self.env.context.get('active_id') asset_id = self.env.context.get("active_id")
asset = self.env['account.asset'].browse(asset_id) asset = self.env["account.asset"].browse(asset_id)
return asset.profile_id.account_plus_value_id return asset.profile_id.account_plus_value_id
@api.model @api.model
def _default_account_min_value_id(self): def _default_account_min_value_id(self):
asset_id = self.env.context.get('active_id') asset_id = self.env.context.get("active_id")
asset = self.env['account.asset'].browse(asset_id) asset = self.env["account.asset"].browse(asset_id)
return asset.profile_id.account_min_value_id return asset.profile_id.account_min_value_id
@api.model @api.model
def _default_account_residual_value_id(self): def _default_account_residual_value_id(self):
asset_id = self.env.context.get('active_id') asset_id = self.env.context.get("active_id")
asset = self.env['account.asset'].browse(asset_id) asset = self.env["account.asset"].browse(asset_id)
return asset.profile_id.account_residual_value_id return asset.profile_id.account_residual_value_id
@api.model @api.model
def _selection_posting_regime(self): def _selection_posting_regime(self):
return[ return [
('residual_value', _('Residual Value')), ("residual_value", _("Residual Value")),
('gain_loss_on_sale', _('Gain/Loss on Sale')), ("gain_loss_on_sale", _("Gain/Loss on Sale")),
] ]
@api.model @api.model
def _get_posting_regime(self): def _get_posting_regime(self):
asset_obj = self.env['account.asset'] asset_obj = self.env["account.asset"]
asset = asset_obj.browse(self.env.context.get('active_id')) asset = asset_obj.browse(self.env.context.get("active_id"))
country = asset and asset.company_id.country_id.code or False country = asset and asset.company_id.country_id.code or False
if country in self._residual_value_regime_countries(): if country in self._residual_value_regime_countries():
return 'residual_value' return "residual_value"
else: else:
return 'gain_loss_on_sale' return "gain_loss_on_sale"
def _residual_value_regime_countries(self): def _residual_value_regime_countries(self):
return ['FR'] return ["FR"]
@api.multi @api.multi
def remove(self): def remove(self):
self.ensure_one() self.ensure_one()
asset_line_obj = self.env['account.asset.line'] asset_line_obj = self.env["account.asset.line"]
asset_id = self.env.context.get('active_id') asset_id = self.env.context.get("active_id")
asset = self.env['account.asset'].browse(asset_id) asset = self.env["account.asset"].browse(asset_id)
asset_ref = asset.code and '%s (ref: %s)' \ asset_ref = (
% (asset.name, asset.code) or asset.name asset.code and "{} (ref: {})".format(asset.name, asset.code) or asset.name
)
if self.env.context.get('early_removal'): if self.env.context.get("early_removal"):
residual_value = self._prepare_early_removal(asset) residual_value = self._prepare_early_removal(asset)
else: else:
residual_value = asset.value_residual residual_value = asset.value_residual
dlines = asset_line_obj.search( dlines = asset_line_obj.search(
[('asset_id', '=', asset.id), ('type', '=', 'depreciate')], [("asset_id", "=", asset.id), ("type", "=", "depreciate")],
order='line_date desc') order="line_date desc",
)
if dlines: if dlines:
last_date = dlines[0].line_date last_date = dlines[0].line_date
else: else:
create_dl = asset_line_obj.search( create_dl = asset_line_obj.search(
[('asset_id', '=', asset.id), ('type', '=', 'create')])[0] [("asset_id", "=", asset.id), ("type", "=", "create")]
)[0]
last_date = create_dl.line_date last_date = create_dl.line_date
if self.date_remove < last_date: if self.date_remove < last_date:
raise UserError( raise UserError(
_("The removal date must be after " _("The removal date must be after " "the last depreciation date.")
"the last depreciation date.")) )
line_name = asset._get_depreciation_entry_name(len(dlines) + 1) line_name = asset._get_depreciation_entry_name(len(dlines) + 1)
journal_id = asset.profile_id.journal_id.id journal_id = asset.profile_id.journal_id.id
@ -164,39 +175,39 @@ class AccountAssetRemove(models.TransientModel):
# create move # create move
move_vals = { move_vals = {
'name': asset.name, "name": asset.name,
'date': date_remove, "date": date_remove,
'ref': line_name, "ref": line_name,
'journal_id': journal_id, "journal_id": journal_id,
'narration': self.note, "narration": self.note,
} }
move = self.env['account.move'].create(move_vals) move = self.env["account.move"].create(move_vals)
# create asset line # create asset line
asset_line_vals = { asset_line_vals = {
'amount': residual_value, "amount": residual_value,
'asset_id': asset_id, "asset_id": asset_id,
'name': line_name, "name": line_name,
'line_date': self.date_remove, "line_date": self.date_remove,
'move_id': move.id, "move_id": move.id,
'type': 'remove', "type": "remove",
} }
asset_line_obj.create(asset_line_vals) asset_line_obj.create(asset_line_vals)
asset.write({'state': 'removed', 'date_remove': self.date_remove}) asset.write({"state": "removed", "date_remove": self.date_remove})
# create move lines # create move lines
move_lines = self._get_removal_data(asset, residual_value) move_lines = self._get_removal_data(asset, residual_value)
move.with_context(allow_asset=True).write({'line_ids': move_lines}) move.with_context(allow_asset=True).write({"line_ids": move_lines})
return { return {
'name': _("Asset '%s' Removal Journal Entry") % asset_ref, "name": _("Asset '%s' Removal Journal Entry") % asset_ref,
'view_type': 'form', "view_type": "form",
'view_mode': 'tree,form', "view_mode": "tree,form",
'res_model': 'account.move', "res_model": "account.move",
'view_id': False, "view_id": False,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'context': self.env.context, "context": self.env.context,
'domain': [('id', '=', move.id)], "domain": [("id", "=", move.id)],
} }
def _prepare_early_removal(self, asset): def _prepare_early_removal(self, asset):
@ -204,15 +215,17 @@ class AccountAssetRemove(models.TransientModel):
Generate last depreciation entry on the day before the removal date. Generate last depreciation entry on the day before the removal date.
""" """
date_remove = self.date_remove date_remove = self.date_remove
asset_line_obj = self.env['account.asset.line'] asset_line_obj = self.env["account.asset.line"]
digits = self.env['decimal.precision'].precision_get('Account') digits = self.env["decimal.precision"].precision_get("Account")
def _dlines(asset): def _dlines(asset):
lines = asset.depreciation_line_ids lines = asset.depreciation_line_ids
dlines = lines.filtered( dlines = lines.filtered(
lambda l: l.type == 'depreciate' and not lambda l: l.type == "depreciate"
l.init_entry and not l.move_check) and not l.init_entry
and not l.move_check
)
dlines = dlines.sorted(key=lambda l: l.line_date) dlines = dlines.sorted(key=lambda l: l.line_date)
return dlines return dlines
@ -225,28 +238,32 @@ class AccountAssetRemove(models.TransientModel):
first_date = first_to_depreciate_dl.line_date first_date = first_to_depreciate_dl.line_date
if date_remove > first_date: if date_remove > first_date:
raise UserError( raise UserError(
_("You can't make an early removal if all the depreciation " _(
"lines for previous periods are not posted.")) "You can't make an early removal if all the depreciation "
"lines for previous periods are not posted."
)
)
if first_to_depreciate_dl.previous_id: if first_to_depreciate_dl.previous_id:
last_depr_date = first_to_depreciate_dl.previous_id.line_date last_depr_date = first_to_depreciate_dl.previous_id.line_date
else: else:
create_dl = asset_line_obj.search( create_dl = asset_line_obj.search(
[('asset_id', '=', asset.id), ('type', '=', 'create')]) [("asset_id", "=", asset.id), ("type", "=", "create")]
)
last_depr_date = create_dl.line_date last_depr_date = create_dl.line_date
period_number_days = (first_date - last_depr_date).days period_number_days = (first_date - last_depr_date).days
new_line_date = date_remove + relativedelta(days=-1) new_line_date = date_remove + relativedelta(days=-1)
to_depreciate_days = (new_line_date - last_depr_date).days to_depreciate_days = (new_line_date - last_depr_date).days
to_depreciate_amount = round( to_depreciate_amount = round(
float(to_depreciate_days) / float(period_number_days) * float(to_depreciate_days)
first_to_depreciate_dl.amount, digits) / float(period_number_days)
* first_to_depreciate_dl.amount,
digits,
)
residual_value = asset.value_residual - to_depreciate_amount residual_value = asset.value_residual - to_depreciate_amount
if to_depreciate_amount: if to_depreciate_amount:
update_vals = { update_vals = {"amount": to_depreciate_amount, "line_date": new_line_date}
'amount': to_depreciate_amount,
'line_date': new_line_date
}
first_to_depreciate_dl.write(update_vals) first_to_depreciate_dl.write(update_vals)
dlines[0].create_move() dlines[0].create_move()
dlines -= dlines[0] dlines -= dlines[0]
@ -262,64 +279,64 @@ class AccountAssetRemove(models.TransientModel):
depr_amount = asset.depreciation_base - residual_value depr_amount = asset.depreciation_base - residual_value
if depr_amount: if depr_amount:
move_line_vals = { move_line_vals = {
'name': asset.name, "name": asset.name,
'account_id': profile.account_depreciation_id.id, "account_id": profile.account_depreciation_id.id,
'debit': depr_amount > 0 and depr_amount or 0.0, "debit": depr_amount > 0 and depr_amount or 0.0,
'credit': depr_amount < 0 and -depr_amount or 0.0, "credit": depr_amount < 0 and -depr_amount or 0.0,
'partner_id': partner_id, "partner_id": partner_id,
'asset_id': asset.id "asset_id": asset.id,
} }
move_lines.append((0, 0, move_line_vals)) move_lines.append((0, 0, move_line_vals))
move_line_vals = { move_line_vals = {
'name': asset.name, "name": asset.name,
'account_id': profile.account_asset_id.id, "account_id": profile.account_asset_id.id,
'debit': (asset.depreciation_base < 0 and -asset "debit": (asset.depreciation_base < 0 and -asset.depreciation_base or 0.0),
.depreciation_base or 0.0), "credit": (asset.depreciation_base > 0 and asset.depreciation_base or 0.0),
'credit': (asset.depreciation_base > 0 and asset "partner_id": partner_id,
.depreciation_base or 0.0), "asset_id": asset.id,
'partner_id': partner_id,
'asset_id': asset.id
} }
move_lines.append((0, 0, move_line_vals)) move_lines.append((0, 0, move_line_vals))
if residual_value: if residual_value:
if self.posting_regime == 'residual_value': if self.posting_regime == "residual_value":
move_line_vals = { move_line_vals = {
'name': asset.name, "name": asset.name,
'account_id': self.account_residual_value_id.id, "account_id": self.account_residual_value_id.id,
'analytic_account_id': asset.account_analytic_id.id, "analytic_account_id": asset.account_analytic_id.id,
'debit': residual_value, "debit": residual_value,
'credit': 0.0, "credit": 0.0,
'partner_id': partner_id, "partner_id": partner_id,
'asset_id': asset.id "asset_id": asset.id,
} }
move_lines.append((0, 0, move_line_vals)) move_lines.append((0, 0, move_line_vals))
elif self.posting_regime == 'gain_loss_on_sale': elif self.posting_regime == "gain_loss_on_sale":
if self.sale_value: if self.sale_value:
sale_value = self.sale_value sale_value = self.sale_value
move_line_vals = { move_line_vals = {
'name': asset.name, "name": asset.name,
'account_id': self.account_sale_id.id, "account_id": self.account_sale_id.id,
'analytic_account_id': asset.account_analytic_id.id, "analytic_account_id": asset.account_analytic_id.id,
'debit': sale_value, "debit": sale_value,
'credit': 0.0, "credit": 0.0,
'partner_id': partner_id, "partner_id": partner_id,
'asset_id': asset.id "asset_id": asset.id,
} }
move_lines.append((0, 0, move_line_vals)) move_lines.append((0, 0, move_line_vals))
balance = self.sale_value - residual_value balance = self.sale_value - residual_value
account_id = (self.account_plus_value_id.id account_id = (
if balance > 0 self.account_plus_value_id.id
else self.account_min_value_id.id) if balance > 0
else self.account_min_value_id.id
)
move_line_vals = { move_line_vals = {
'name': asset.name, "name": asset.name,
'account_id': account_id, "account_id": account_id,
'analytic_account_id': asset.account_analytic_id.id, "analytic_account_id": asset.account_analytic_id.id,
'debit': balance < 0 and -balance or 0.0, "debit": balance < 0 and -balance or 0.0,
'credit': balance > 0 and balance or 0.0, "credit": balance > 0 and balance or 0.0,
'partner_id': partner_id, "partner_id": partner_id,
'asset_id': asset.id "asset_id": asset.id,
} }
move_lines.append((0, 0, move_line_vals)) move_lines.append((0, 0, move_line_vals))
return move_lines return move_lines