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