diff --git a/addons/base_accounting_kit/models/account_asset_depreciation_line.py b/addons/base_accounting_kit/models/account_asset_depreciation_line.py new file mode 100644 index 0000000..96fb842 --- /dev/null +++ b/addons/base_accounting_kit/models/account_asset_depreciation_line.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import api, fields, models, _ +from odoo.exceptions import UserError +from odoo.tools import float_compare + + +class AccountAssetDepreciationLine(models.Model): + """Model for managing asset depreciation lines in the accounting system.""" + _name = 'account.asset.depreciation.line' + _description = 'Asset depreciation line' + + name = fields.Char(string='Depreciation Name', required=True, index=True) + sequence = fields.Integer(required=True) + asset_id = fields.Many2one('account.asset.asset', string='Asset', + required=True, ondelete='cascade') + parent_state = fields.Selection(related='asset_id.state', + string='State of Asset') + amount = fields.Float(string='Current Depreciation', + required=True) + remaining_value = fields.Float(string='Next Period Depreciation', + required=True) + depreciated_value = fields.Float(string='Cumulative Depreciation', + required=True) + depreciation_date = fields.Date('Depreciation Date', index=True) + move_id = fields.Many2one('account.move', string='Depreciation Entry') + move_check = fields.Boolean(compute='_get_move_check', string='Linked', + store=True) + move_posted_check = fields.Boolean(compute='_get_move_posted_check', + string='Posted', store=True) + + @api.depends('move_id') + def _get_move_check(self): + """Compute the 'move_check' field based on the presence of 'move_id' + for each record in the 'AccountAssetDepreciationLine' class.""" + for line in self: + line.move_check = bool(line.move_id) + + @api.depends('move_id.state') + def _get_move_posted_check(self): + """Compute the 'move_posted_check' field based on the state of 'move_id' + for each record in the 'AccountAssetDepreciationLine' class.""" + for line in self: + line.move_posted_check = True if line.move_id and line.move_id.state == 'posted' else False + + def create_move(self, post_move=True): + """Create accounting moves for asset depreciation lines.""" + created_moves = self.env['account.move'] + prec = self.env['decimal.precision'].precision_get('Account') + if self.mapped('move_id'): + raise UserError(_( + 'This depreciation is already linked to a journal entry! Please post or delete it.')) + for line in self: + asset_id = line.asset_id + depreciation_date = self.env.context.get( + 'depreciation_date') or line.depreciation_date or fields.Date.context_today( + self) + company_currency = asset_id.company_id.currency_id + current_currency = asset_id.currency_id + amount = current_currency._convert(line.amount, company_currency, + line.asset_id.company_id, + depreciation_date) + asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids)) + partner = self.env['res.partner']._find_accounting_partner(line.asset_id.partner_id) + move_line_1 = { + 'name': asset_name, + 'account_id': asset_id.account_depreciation_id.id, + 'debit': 0.0 if float_compare(amount, 0.0, + precision_digits=prec) > 0 else -amount, + 'credit': amount if float_compare(amount, 0.0, + precision_digits=prec) > 0 else 0.0, + 'journal_id': asset_id.journal_id.id, + 'partner_id': partner.id, + 'currency_id': company_currency != current_currency and current_currency.id or company_currency.id, + 'amount_currency': company_currency != current_currency and - 1.0 * line.amount or 0.0, + } + move_line_2 = { + 'name': asset_name, + 'account_id': asset_id.account_depreciation_expense_id.id, + 'credit': 0.0 if float_compare(amount, 0.0, + precision_digits=prec) > 0 else -amount, + 'debit': amount if float_compare(amount, 0.0, + precision_digits=prec) > 0 else 0.0, + 'journal_id': asset_id.journal_id.id, + 'partner_id': partner.id, + 'currency_id': company_currency != current_currency and current_currency.id or company_currency.id, + 'amount_currency': company_currency != current_currency and line.amount or 0.0, + } + line_ids = [(0, 0, { + 'account_id': asset_id.account_depreciation_id.id, + 'partner_id': partner.id, + 'credit': amount if float_compare(amount, 0.0, + precision_digits=prec) > 0 else 0.0, + }), (0, 0, { + 'account_id': asset_id.account_depreciation_expense_id.id, + 'partner_id': partner.id, + 'debit': amount if float_compare(amount, 0.0, + precision_digits=prec) > 0 else 0.0, + })] + move = self.env['account.move'].create({ + 'ref': line.asset_id.code, + 'date': depreciation_date or False, + 'journal_id': asset_id.journal_id.id, + 'line_ids': line_ids, + }) + for move_line in move.line_ids: + if move_line.account_id.id == move_line_1['account_id']: + move_line.write({'credit': move_line_1['credit'], + 'debit': move_line_1['debit']}) + elif move_line.account_id.id == move_line_2['account_id']: + move_line.write({'debit': move_line_2['debit'], + 'credit': move_line_2['credit']}) + if move.line_ids.filtered( + lambda x: x.name == 'Automatic Balancing Line'): + move.line_ids.filtered( + lambda x: x.name == 'Automatic Balancing Line').unlink() + line.write({'move_id': move.id, 'move_check': True}) + created_moves |= move + + if post_move and created_moves: + created_moves.filtered(lambda m: any( + m.asset_depreciation_ids.mapped( + 'asset_id.open_asset'))).post() + return [x.id for x in created_moves] + + def create_grouped_move(self, post_move=True): + """Create a grouped accounting move for asset depreciation lines.""" + if not self.exists(): + return [] + created_moves = self.env['account.move'] + category_id = self[ + 0].asset_id.category_id # we can suppose that all lines have the same category + depreciation_date = self.env.context.get( + 'depreciation_date') or fields.Date.context_today(self) + amount = 0.0 + for line in self: + # Sum amount of all depreciation lines + company_currency = line.asset_id.company_id.currency_id + current_currency = line.asset_id.currency_id + amount += current_currency.compute(line.amount, company_currency) + + name = category_id.name + _(' (grouped)') + move_line_1 = { + 'name': name, + 'account_id': category_id.account_depreciation_id.id, + 'debit': 0.0, + 'credit': amount, + 'journal_id': category_id.journal_id.id, + 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False, + } + move_line_2 = { + 'name': name, + 'account_id': category_id.account_depreciation_expense_id.id, + 'credit': 0.0, + 'debit': amount, + 'journal_id': category_id.journal_id.id, + 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False, + } + move_vals = { + 'ref': category_id.name, + 'date': depreciation_date or False, + 'journal_id': category_id.journal_id.id, + 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], + } + move = self.env['account.move'].create(move_vals) + self.write({'move_id': move.id, 'move_check': True}) + created_moves |= move + + if post_move and created_moves: + self.post_lines_and_close_asset() + created_moves.post() + return [x.id for x in created_moves] + + def post_lines_and_close_asset(self): + # we re-evaluate the assets to determine whether we can close them + # `message_post` invalidates the (whole) cache + # preprocess the assets and lines in which a message should be posted, + # and then post in batch will prevent the re-fetch of the same data over and over. + assets_to_close = self.env['account.asset.asset'] + for line in self: + asset = line.asset_id + if asset.currency_id.is_zero(asset.value_residual): + assets_to_close |= asset + self.log_message_when_posted() + assets_to_close.write({'state': 'close'}) + for asset in assets_to_close: + asset.message_post(body=_("Document closed.")) + + def log_message_when_posted(self): + """Format and post messages for asset depreciation lines that are posted.""" + def _format_message(message_description, tracked_values): + message = '' + if message_description: + message = '%s' % message_description + for name, values in tracked_values.items(): + message += '
    • %s: ' % name + message += '%s
' % values + return message + + # `message_post` invalidates the (whole) cache + # preprocess the assets in which messages should be posted, + # and then post in batch will prevent the re-fetch of the same data over and over. + assets_to_post = {} + for line in self: + if line.move_id and line.move_id.state == 'draft': + partner_name = line.asset_id.partner_id.name + currency_name = line.asset_id.currency_id.name + msg_values = {_('Currency'): currency_name, + _('Amount'): line.amount} + if partner_name: + msg_values[_('Partner')] = partner_name + msg = _format_message(_('Depreciation line posted.'), + msg_values) + assets_to_post.setdefault(line.asset_id, []).append(msg) + for asset, messages in assets_to_post.items(): + for msg in messages: + asset.message_post(body=msg) + + # def unlink(self): + # """Check if the depreciation line is linked to a posted move before deletion.""" + # for record in self: + # if record.move_check: + # if record.asset_id.category_id.type == 'purchase': + # msg = _("You cannot delete posted depreciation lines.") + # else: + # msg = _("You cannot delete posted installment lines.") + # raise UserError(msg) + # return super(AccountAssetDepreciationLine, self).unlink()