From c2677e5e24f9054e5fa6e152befffe6a47aa7af6 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Wed, 22 May 2024 10:25:19 +0200 Subject: [PATCH 1/3] [REF] l10n_it_asset_management: Extract common tests setup --- l10n_it_asset_management/tests/common.py | 396 ++++++++++++++++ .../tests/test_assets_management.py | 421 +----------------- 2 files changed, 413 insertions(+), 404 deletions(-) create mode 100644 l10n_it_asset_management/tests/common.py diff --git a/l10n_it_asset_management/tests/common.py b/l10n_it_asset_management/tests/common.py new file mode 100644 index 00000000000..b307a145b18 --- /dev/null +++ b/l10n_it_asset_management/tests/common.py @@ -0,0 +1,396 @@ +# Copyright 2021 Sergio Corato +# Copyright 2022 Simone Rubino - TAKOBI +# Copyright 2023 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import date + +from odoo.fields import Command, first +from odoo.tests.common import Form, TransactionCase + + +class Common(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.data_account_type_current_assets = "asset_current" + + cls.asset_fixed_account = cls.env["account.account"].search( + [ + ( + "account_type", + "=", + "asset_fixed", + ) + ], + limit=1, + ) + cls.expense_account = cls.env["account.account"].search( + [ + ( + "account_type", + "=", + "expense", + ) + ], + limit=1, + ) + cls.asset_non_current_account = cls.env["account.account"].search( + [ + ( + "account_type", + "=", + "asset_non_current", + ) + ], + limit=1, + ) + cls.income_account = cls.env["account.account"].search( + [ + ( + "account_type", + "=", + "income", + ) + ], + limit=1, + ) + + cls.general_journal = cls.env["account.journal"].search( + [("type", "=", "general")], limit=1 + ) + cls.purchase_journal = cls.env["account.journal"].search( + [ + ("type", "=", "purchase"), + ], + limit=1, + ) + cls.sale_journal = cls.env["account.journal"].search( + [ + ("type", "=", "sale"), + ], + limit=1, + ) + + cls.civilistico_asset_dep_type = cls.env.ref( + "l10n_it_asset_management.ad_type_civilistico" + ) + + cls.materiale_asset_dep_mode = cls.env.ref( + "l10n_it_asset_management.ad_mode_materiale" + ) + + cls.asset_category_1 = cls.env["asset.category"].create( + { + "name": "Asset category 1", + "asset_account_id": cls.asset_fixed_account.id, + "depreciation_account_id": cls.expense_account.id, + "fund_account_id": cls.asset_non_current_account.id, + "gain_account_id": cls.income_account.id, + "journal_id": cls.general_journal.id, + "loss_account_id": cls.expense_account.id, + "type_ids": [ + Command.create( + { + "depreciation_type_id": cls.civilistico_asset_dep_type.id, + "mode_id": cls.materiale_asset_dep_mode.id, + }, + ) + ], + } + ) + cls.tax_account = cls.env["account.account"].create( + { + "name": "Deductable tax", + "code": "DEDTAX", + "account_type": cls.data_account_type_current_assets, + } + ) + cls.tax_22_partial_60 = cls.env["account.tax"].create( + { + "name": "22% deductable partial 60%", + "type_tax_use": "purchase", + "amount_type": "percent", + "amount": 22, + "invoice_repartition_line_ids": [ + Command.create( + { + "factor_percent": 100, + "repartition_type": "base", + }, + ), + Command.create( + { + "factor_percent": 60, + "repartition_type": "tax", + "account_id": cls.tax_account.id, + }, + ), + Command.create( + { + "factor_percent": 40, + "repartition_type": "tax", + }, + ), + ], + "refund_repartition_line_ids": [ + Command.create( + { + "factor_percent": 100, + "repartition_type": "base", + }, + ), + Command.create( + { + "factor_percent": 60, + "repartition_type": "tax", + "account_id": cls.tax_account.id, + }, + ), + Command.create( + { + "factor_percent": 40, + "repartition_type": "tax", + }, + ), + ], + } + ) + cls.bank_account = cls.env["account.account"].create( + { + "code": "TBA", + "name": "Test Bank Account", + "account_type": "asset_cash", + } + ) + cls.env.user.groups_id += cls.env.ref("account.group_account_readonly") + + def _create_asset(self, asset_date=None): + asset = self.env["asset.asset"].create( + { + "name": "Test asset", + "category_id": self.asset_category_1.id, + "company_id": self.env.ref("base.main_company").id, + "currency_id": self.env.ref("base.main_company").currency_id.id, + "purchase_amount": 1000.0, + "purchase_date": asset_date, + } + ) + return asset + + def _depreciate_asset_wizard( + self, + asset, + date_dep, + period="year", + period_count=None, + override_journal=None, + ): + if override_journal is None: + override_journal = self.env["account.journal"].browse() + wiz_vals = asset.with_context( + **{"allow_reload_window": True} + ).launch_wizard_generate_depreciations() + wiz = ( + self.env["wizard.asset.generate.depreciation"] + .with_context(**wiz_vals["context"]) + .create( + { + "date_dep": date_dep, + "period": period, + "period_count": period_count, + "journal_id": override_journal.id, + } + ) + ) + return wiz + + def _depreciate_asset( + self, + asset, + date_dep, + period="year", + period_count=None, + override_journal=None, + ): + wiz = self._depreciate_asset_wizard( + asset, + date_dep, + period=period, + period_count=period_count, + override_journal=override_journal, + ) + wiz.do_generate() + + def _create_purchase_invoice(self, invoice_date, tax_ids=False, amount=7000): + invoice_line_vals = { + "account_id": self.asset_category_1.asset_account_id.id, + "quantity": 1, + "price_unit": amount, + } + if tax_ids: + invoice_line_vals.update({"tax_ids": tax_ids}) + purchase_invoice = self.env["account.move"].create( + { + "move_type": "in_invoice", + "invoice_date": invoice_date, + "partner_id": self.env.ref("base.partner_demo").id, + "journal_id": self.env["account.journal"] + .search( + [ + ("type", "=", "purchase"), + ], + limit=1, + ) + .id, + "invoice_line_ids": [ + Command.create( + invoice_line_vals, + ) + ], + } + ) + purchase_invoice.action_post() + self.assertEqual(purchase_invoice.state, "posted") + return purchase_invoice + + def _create_sale_invoice(self, asset, amount=7000, invoice_date=None, post=True): + sale_invoice = self.env["account.move"].create( + { + "move_type": "out_invoice", + "invoice_date": invoice_date, + "partner_id": self.env.ref("base.partner_demo").id, + "journal_id": self.sale_journal.id, + "invoice_line_ids": [ + Command.create( + { + "account_id": asset.category_id.asset_account_id.id, + "quantity": 1, + "price_unit": amount, + }, + ) + ], + } + ) + if post: + sale_invoice.action_post() + return sale_invoice + + def _create_entry(self, account, amount, post=True): + """Create an entry that adds `amount` to `account`.""" + entry_form = Form(self.env["account.move"]) + with entry_form.line_ids.new() as asset_line: + asset_line.account_id = account + asset_line.debit = amount + with entry_form.line_ids.new() as bank_line: + bank_line.account_id = self.bank_account + entry = entry_form.save() + + if post: + entry.action_post() + + self.assertEqual(entry.move_type, "entry") + return entry + + def _civil_depreciate_asset(self, asset): + # Keep only one civil depreciation + civil_depreciation_type = self.env.ref( + "l10n_it_asset_management.ad_type_civilistico" + ) + civil_depreciation = first( + asset.depreciation_ids.filtered( + lambda d: d.type_id == civil_depreciation_type + ) + ) + (asset.depreciation_ids - civil_depreciation).unlink() + + civil_depreciation.line_ids = [ + Command.clear(), + Command.create( + { + "name": "2019", + "date": date(2019, 12, 31), + "move_type": "depreciated", + "amount": 500, + }, + ), + Command.create( + { + "name": "2020", + "date": date(2020, 12, 31), + "move_type": "depreciated", + "amount": 500, + }, + ), + ] + return True + + def _generate_fiscal_years(self, start_date, end_date): + fiscal_years = range( + start_date.year, + end_date.year + 1, + ) + fiscal_years_values = list() + for fiscal_year in fiscal_years: + fiscal_year_values = { + "name": "Fiscal Year %d" % fiscal_year, + "date_from": date(fiscal_year, 1, 1), + "date_to": date(fiscal_year, 12, 31), + } + fiscal_years_values.append(fiscal_year_values) + return self.env["account.fiscal.year"].create(fiscal_years_values) + + def _get_report_values(self, report_type): + if report_type == "previsional": + wizard_model = "wizard.asset.previsional.report" + report_model = "report_asset_previsional" + export_method = "export_asset_previsional_report" + elif report_type == "journal": + wizard_model = "wizard.asset.journal.report" + report_model = "report_asset_journal" + export_method = "export_asset_journal_report" + else: + raise Exception("Report can only be 'journal' or 'previsional'") + return export_method, report_model, wizard_model + + def _get_report(self, report_date, report_type): + export_method, report_model, wizard_model = self._get_report_values(report_type) + + wiz = self.env[wizard_model].create( + { + "date": report_date, + } + ) + report_result = getattr(wiz, export_method)() + report_ids = report_result["context"]["report_action"]["context"]["active_ids"] + report = self.env[report_model].browse(report_ids) + return report + + def _get_move_asset_wizard(self, move, link_management_type, wiz_values=None): + """Get the wizard that links `move` to an asset + with mode `link_management_type`. + `wiz_values` are values to be set in the wizard. + """ + if wiz_values is None: + wiz_values = {} + + wiz_action_values = move.open_wizard_manage_asset() + wiz_form = Form( + self.env["wizard.account.move.manage.asset"].with_context( + **wiz_action_values["context"] + ) + ) + wiz_form.management_type = link_management_type + for field_name, field_value in wiz_values.items(): + setattr(wiz_form, field_name, field_value) + wiz = wiz_form.save() + return wiz + + def _link_asset_move(self, move, link_management_type, wiz_values=None): + """Link `move` to an asset with mode `link_management_type`. + `wiz_values` are values to be set in the wizard. + """ + wiz = self._get_move_asset_wizard( + move, link_management_type, wiz_values=wiz_values + ) + return wiz.link_asset() diff --git a/l10n_it_asset_management/tests/test_assets_management.py b/l10n_it_asset_management/tests/test_assets_management.py index 50c02359133..41f80ac93cc 100644 --- a/l10n_it_asset_management/tests/test_assets_management.py +++ b/l10n_it_asset_management/tests/test_assets_management.py @@ -6,309 +6,13 @@ from odoo import fields from odoo.exceptions import ValidationError -from odoo.fields import Command, first -from odoo.tests import Form -from odoo.tests.common import TransactionCase +from odoo.fields import Command from odoo.tools.date_utils import relativedelta +from .common import Common -class TestAssets(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.data_account_type_current_assets = "asset_current" - cls.asset_category_1 = cls.env["asset.category"].create( - { - "name": "Asset category 1", - "asset_account_id": cls.env["account.account"] - .search( - [ - ( - "account_type", - "=", - "asset_fixed", - ) - ], - limit=1, - ) - .id, - "depreciation_account_id": cls.env["account.account"] - .search( - [ - ( - "account_type", - "=", - "expense", - ) - ], - limit=1, - ) - .id, - "fund_account_id": cls.env["account.account"] - .search( - [ - ( - "account_type", - "=", - "asset_non_current", - ) - ], - limit=1, - ) - .id, - "gain_account_id": cls.env["account.account"] - .search( - [ - ( - "account_type", - "=", - "income", - ) - ], - limit=1, - ) - .id, - "journal_id": cls.env["account.journal"] - .search([("type", "=", "general")], limit=1) - .id, - "loss_account_id": cls.env["account.account"] - .search( - [ - ( - "account_type", - "=", - "expense", - ) - ], - limit=1, - ) - .id, - "type_ids": [ - Command.create( - { - "depreciation_type_id": cls.env.ref( - "l10n_it_asset_management.ad_type_civilistico" - ).id, - "mode_id": cls.env.ref( - "l10n_it_asset_management.ad_mode_materiale" - ).id, - }, - ) - ], - } - ) - cls.tax_account = cls.env["account.account"].create( - { - "name": "Deductable tax", - "code": "DEDTAX", - "account_type": cls.data_account_type_current_assets, - } - ) - cls.tax_22_partial_60 = cls.env["account.tax"].create( - { - "name": "22% deductable partial 60%", - "type_tax_use": "purchase", - "amount_type": "percent", - "amount": 22, - "invoice_repartition_line_ids": [ - Command.create( - { - "factor_percent": 100, - "repartition_type": "base", - }, - ), - Command.create( - { - "factor_percent": 60, - "repartition_type": "tax", - "account_id": cls.tax_account.id, - }, - ), - Command.create( - { - "factor_percent": 40, - "repartition_type": "tax", - }, - ), - ], - "refund_repartition_line_ids": [ - Command.create( - { - "factor_percent": 100, - "repartition_type": "base", - }, - ), - Command.create( - { - "factor_percent": 60, - "repartition_type": "tax", - "account_id": cls.tax_account.id, - }, - ), - Command.create( - { - "factor_percent": 40, - "repartition_type": "tax", - }, - ), - ], - } - ) - cls.bank_account = cls.env["account.account"].create( - { - "code": "TBA", - "name": "Test Bank Account", - "account_type": "asset_cash", - } - ) - cls.env.user.groups_id += cls.env.ref("account.group_account_readonly") - cls.sale_journal = cls.env["account.journal"].search( - [ - ("type", "=", "sale"), - ], - limit=1, - ) - - def _create_asset(self, asset_date=None): - asset = self.env["asset.asset"].create( - { - "name": "Test asset", - "category_id": self.asset_category_1.id, - "company_id": self.env.ref("base.main_company").id, - "currency_id": self.env.ref("base.main_company").currency_id.id, - "purchase_amount": 1000.0, - "purchase_date": asset_date, - } - ) - return asset - - def _depreciate_asset_wizard( - self, - asset, - date_dep, - period="year", - period_count=None, - override_journal=None, - ): - if override_journal is None: - override_journal = self.env["account.journal"].browse() - wiz_vals = asset.with_context( - **{"allow_reload_window": True} - ).launch_wizard_generate_depreciations() - wiz = ( - self.env["wizard.asset.generate.depreciation"] - .with_context(**wiz_vals["context"]) - .create( - { - "date_dep": date_dep, - "period": period, - "period_count": period_count, - "journal_id": override_journal.id, - } - ) - ) - return wiz - - def _depreciate_asset( - self, - asset, - date_dep, - period="year", - period_count=None, - override_journal=None, - ): - wiz = self._depreciate_asset_wizard( - asset, - date_dep, - period=period, - period_count=period_count, - override_journal=override_journal, - ) - wiz.do_generate() - - def _create_purchase_invoice(self, invoice_date, tax_ids=False, amount=7000): - invoice_line_vals = { - "account_id": self.asset_category_1.asset_account_id.id, - "quantity": 1, - "price_unit": amount, - } - if tax_ids: - invoice_line_vals.update({"tax_ids": tax_ids}) - purchase_invoice = self.env["account.move"].create( - { - "move_type": "in_invoice", - "invoice_date": invoice_date, - "partner_id": self.env.ref("base.partner_demo").id, - "journal_id": self.env["account.journal"] - .search( - [ - ("type", "=", "purchase"), - ], - limit=1, - ) - .id, - "invoice_line_ids": [ - Command.create( - invoice_line_vals, - ) - ], - } - ) - purchase_invoice.action_post() - self.assertEqual(purchase_invoice.state, "posted") - return purchase_invoice - - def _create_sale_invoice(self, asset, amount=7000, invoice_date=None, post=True): - sale_invoice = self.env["account.move"].create( - { - "move_type": "out_invoice", - "invoice_date": invoice_date, - "partner_id": self.env.ref("base.partner_demo").id, - "journal_id": self.sale_journal.id, - "invoice_line_ids": [ - Command.create( - { - "account_id": asset.category_id.asset_account_id.id, - "quantity": 1, - "price_unit": amount, - }, - ) - ], - } - ) - if post: - sale_invoice.action_post() - return sale_invoice - - def _create_entry(self, account, amount, post=True): - """Create an entry that adds `amount` to `account`.""" - entry_form = Form(self.env["account.move"]) - with entry_form.line_ids.new() as asset_line: - asset_line.account_id = account - asset_line.debit = amount - with entry_form.line_ids.new() as bank_line: - bank_line.account_id = self.bank_account - entry = entry_form.save() - - if post: - entry.action_post() - - self.assertEqual(entry.move_type, "entry") - return entry - - def _update_asset(self, entry, asset): - """Execute the wizard on `entry` to update `asset`.""" - wizard_action = entry.open_wizard_manage_asset() - wizard_model = self.env[wizard_action["res_model"]] - wizard_context = wizard_action["context"] - - wizard_form = Form(wizard_model.with_context(**wizard_context)) - wizard_form.management_type = "update" - wizard_form.asset_id = asset - wizard = wizard_form.save() - - return wizard.link_asset() +class TestAssets(Common): def test_00_create_asset_depreciate_and_sale(self): today = fields.Date.today() asset = self._create_asset(today + relativedelta(years=-1)) @@ -777,7 +481,13 @@ def test_entry_in_update_asset(self): self.assertFalse(asset.asset_accounting_info_ids) # Act - self._update_asset(entry, asset) + self._link_asset_move( + entry, + "update", + wiz_values={ + "asset_id": asset, + }, + ) # Assert accounting_info = asset.asset_accounting_info_ids @@ -797,7 +507,13 @@ def test_entry_out_update_asset(self): self.assertFalse(asset.asset_accounting_info_ids) # Act - self._update_asset(entry, asset) + self._link_asset_move( + entry, + "update", + wiz_values={ + "asset_id": asset, + }, + ) # Assert accounting_info = asset.asset_accounting_info_ids @@ -807,80 +523,6 @@ def test_entry_out_update_asset(self): depreciation_info.amount_residual, asset.purchase_amount - removed_amount ) - def _civil_depreciate_asset(self, asset): - # Keep only one civil depreciation - civil_depreciation_type = self.env.ref( - "l10n_it_asset_management.ad_type_civilistico" - ) - civil_depreciation = first( - asset.depreciation_ids.filtered( - lambda d: d.type_id == civil_depreciation_type - ) - ) - (asset.depreciation_ids - civil_depreciation).unlink() - - civil_depreciation.line_ids = [ - Command.clear(), - Command.create( - { - "name": "2019", - "date": date(2019, 12, 31), - "move_type": "depreciated", - "amount": 500, - }, - ), - Command.create( - { - "name": "2020", - "date": date(2020, 12, 31), - "move_type": "depreciated", - "amount": 500, - }, - ), - ] - return True - - def _generate_fiscal_years(self, start_date, end_date): - fiscal_years = range( - start_date.year, - end_date.year + 1, - ) - fiscal_years_values = list() - for fiscal_year in fiscal_years: - fiscal_year_values = { - "name": "Fiscal Year %d" % fiscal_year, - "date_from": date(fiscal_year, 1, 1), - "date_to": date(fiscal_year, 12, 31), - } - fiscal_years_values.append(fiscal_year_values) - return self.env["account.fiscal.year"].create(fiscal_years_values) - - def _get_report_values(self, report_type): - if report_type == "previsional": - wizard_model = "wizard.asset.previsional.report" - report_model = "report_asset_previsional" - export_method = "export_asset_previsional_report" - elif report_type == "journal": - wizard_model = "wizard.asset.journal.report" - report_model = "report_asset_journal" - export_method = "export_asset_journal_report" - else: - raise Exception("Report can only be 'journal' or 'previsional'") - return export_method, report_model, wizard_model - - def _get_report(self, report_date, report_type): - export_method, report_model, wizard_model = self._get_report_values(report_type) - - wiz = self.env[wizard_model].create( - { - "date": report_date, - } - ) - report_result = getattr(wiz, export_method)() - report_ids = report_result["context"]["report_action"]["context"]["active_ids"] - report = self.env[report_model].browse(report_ids) - return report - def test_journal_prev_year(self): """ Previous year depreciation considers depreciation of all previous years @@ -1043,35 +685,6 @@ def test_override_journal(self): account_move = asset.depreciation_ids.line_ids.move_id self.assertEqual(account_move.journal_id, depreciate_asset_wizard.journal_id) - def _get_move_asset_wizard(self, move, link_management_type, wiz_values=None): - """Get the wizard that links `move` to an asset - with mode `link_management_type`. - `wiz_values` are values to be set in the wizard. - """ - if wiz_values is None: - wiz_values = {} - - wiz_action_values = move.open_wizard_manage_asset() - wiz_form = Form( - self.env["wizard.account.move.manage.asset"].with_context( - **wiz_action_values["context"] - ) - ) - wiz_form.management_type = link_management_type - for field_name, field_value in wiz_values.items(): - setattr(wiz_form, field_name, field_value) - wiz = wiz_form.save() - return wiz - - def _link_asset_move(self, move, link_management_type, wiz_values=None): - """Link `move` to an asset with mode `link_management_type`. - `wiz_values` are values to be set in the wizard. - """ - wiz = self._get_move_asset_wizard( - move, link_management_type, wiz_values=wiz_values - ) - return wiz.link_asset() - def test_same_asset_report_residual_partial_depreciation(self): """ Partially depreciate an asset, From e6dd81a4bc07e6005ae4a00d88cce3978f9ff097 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Tue, 21 May 2024 10:47:32 +0200 Subject: [PATCH 2/3] [IMP] l10n_it_asset_management: Specific asset depreciation account Allow to use a different account in the asset depreciation than the asset category account --- .../models/asset_depreciation.py | 45 +++++++++++++++++++ .../models/asset_depreciation_line.py | 6 +-- l10n_it_asset_management/tests/__init__.py | 1 + .../tests/test_asset_depreciation.py | 42 +++++++++++++++++ .../views/asset_depreciation.xml | 12 +++++ 5 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 l10n_it_asset_management/tests/test_asset_depreciation.py diff --git a/l10n_it_asset_management/models/asset_depreciation.py b/l10n_it_asset_management/models/asset_depreciation.py index 17e085e79e9..78ef4ebbb9e 100644 --- a/l10n_it_asset_management/models/asset_depreciation.py +++ b/l10n_it_asset_management/models/asset_depreciation.py @@ -139,6 +139,51 @@ class AssetDepreciation(models.Model): zero_depreciation_until = fields.Date(string="Zero Depreciation Up To") + depreciation_account_id = fields.Many2one( + comodel_name="account.account", + compute="_compute_depreciation_account_id", + readonly=False, + store=True, + string="Depreciation Account", + ) + gain_account_id = fields.Many2one( + comodel_name="account.account", + compute="_compute_gain_account_id", + readonly=False, + store=True, + string="Capital Gain Account", + ) + loss_account_id = fields.Many2one( + comodel_name="account.account", + compute="_compute_loss_account_id", + readonly=False, + store=True, + string="Capital Loss Account", + ) + + @api.depends( + "asset_id.category_id", + ) + def _compute_depreciation_account_id(self): + for dep in self: + dep.depreciation_account_id = ( + dep.asset_id.category_id.depreciation_account_id + ) + + @api.depends( + "asset_id.category_id", + ) + def _compute_gain_account_id(self): + for dep in self: + dep.gain_account_id = dep.asset_id.category_id.gain_account_id + + @api.depends( + "asset_id.category_id", + ) + def _compute_loss_account_id(self): + for dep in self: + dep.loss_account_id = dep.asset_id.category_id.loss_account_id + @api.model_create_multi def create(self, vals_list): depreciations = self.browse() diff --git a/l10n_it_asset_management/models/asset_depreciation_line.py b/l10n_it_asset_management/models/asset_depreciation_line.py index 1dd53e90311..9f13c11eb15 100644 --- a/l10n_it_asset_management/models/asset_depreciation_line.py +++ b/l10n_it_asset_management/models/asset_depreciation_line.py @@ -400,7 +400,7 @@ def get_depreciated_account_move_line_vals(self): # Asset depreciation if not self.partial_dismissal: credit_account_id = self.asset_id.category_id.fund_account_id.id - debit_account_id = self.asset_id.category_id.depreciation_account_id.id + debit_account_id = self.depreciation_id.depreciation_account_id.id # Asset partial dismissal else: @@ -427,7 +427,7 @@ def get_depreciated_account_move_line_vals(self): def get_gain_account_move_line_vals(self): self.ensure_one() credit_line_vals = { - "account_id": self.asset_id.category_id.gain_account_id.id, + "account_id": self.depreciation_id.gain_account_id.id, "credit": self.amount, "debit": 0.0, "currency_id": self.currency_id.id, @@ -462,7 +462,7 @@ def get_loss_account_move_line_vals(self): "name": " - ".join((self.asset_id.make_name(), self.name)), } debit_line_vals = { - "account_id": self.asset_id.category_id.loss_account_id.id, + "account_id": self.depreciation_id.loss_account_id.id, "credit": 0.0, "debit": self.amount, "currency_id": self.currency_id.id, diff --git a/l10n_it_asset_management/tests/__init__.py b/l10n_it_asset_management/tests/__init__.py index 5ddb45a9e1e..c026ba7a131 100644 --- a/l10n_it_asset_management/tests/__init__.py +++ b/l10n_it_asset_management/tests/__init__.py @@ -1,3 +1,4 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import test_assets_management +from . import test_asset_depreciation diff --git a/l10n_it_asset_management/tests/test_asset_depreciation.py b/l10n_it_asset_management/tests/test_asset_depreciation.py new file mode 100644 index 00000000000..435aee7202e --- /dev/null +++ b/l10n_it_asset_management/tests/test_asset_depreciation.py @@ -0,0 +1,42 @@ +# Copyright 2024 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import date + +from .common import Common + + +class TestAssetDepreciation(Common): + def test_line_accounts(self): + """Accounts can be overridden in asset depreciation. + Overridden accounts are used in generated moves. + """ + new_depreciation_account = self.expense_account.copy() + + purchase_date = date(2020, month=1, day=1) + asset = self._create_asset(purchase_date) + + depreciation_date = date(2020, month=12, day=31) + asset_depreciation = asset.depreciation_ids.filtered( + lambda x, dev_type=self.civilistico_asset_dep_type: x.type_id == dev_type + ) + asset_depreciation.percentage = 20.0 + # pre-condition: depreciation accounts default to category accounts + self.assertEqual( + asset_depreciation.depreciation_account_id, + asset.category_id.depreciation_account_id, + ) + self.assertEqual( + asset_depreciation.gain_account_id, asset.category_id.gain_account_id + ) + self.assertEqual( + asset_depreciation.loss_account_id, asset.category_id.loss_account_id + ) + + # Act: change depreciation account and generate move + asset_depreciation.depreciation_account_id = new_depreciation_account + self._depreciate_asset(asset, depreciation_date) + + # Assert: new account is used in generated move + depreciation_move = asset_depreciation.line_ids.move_id + self.assertIn(new_depreciation_account, depreciation_move.line_ids.account_id) diff --git a/l10n_it_asset_management/views/asset_depreciation.xml b/l10n_it_asset_management/views/asset_depreciation.xml index 3193b56bd33..7f6f73bf097 100644 --- a/l10n_it_asset_management/views/asset_depreciation.xml +++ b/l10n_it_asset_management/views/asset_depreciation.xml @@ -46,6 +46,18 @@ name="base_coeff" attrs="{'readonly': [('state', '!=', 'non_depreciated')]}" /> + + + Date: Wed, 22 May 2024 12:35:10 +0200 Subject: [PATCH 3/3] [FIX] l10n_it_asset_management: Gain/Loss proportional to depreciation --- .../tests/test_asset_depreciation.py | 191 ++++++++++++++++++ .../wizard/account_move_manage_asset.py | 21 +- 2 files changed, 209 insertions(+), 3 deletions(-) diff --git a/l10n_it_asset_management/tests/test_asset_depreciation.py b/l10n_it_asset_management/tests/test_asset_depreciation.py index 435aee7202e..b3b355fc95d 100644 --- a/l10n_it_asset_management/tests/test_asset_depreciation.py +++ b/l10n_it_asset_management/tests/test_asset_depreciation.py @@ -40,3 +40,194 @@ def test_line_accounts(self): # Assert: new account is used in generated move depreciation_move = asset_depreciation.line_ids.move_id self.assertIn(new_depreciation_account, depreciation_move.line_ids.account_id) + + def test_depreciate_sale_loss_coefficient(self): + """Depreciate and sale an asset with a sale invoice. + Loss is proportional to the depreciation coefficient.""" + # Arrange + asset_dep_type = self.civilistico_asset_dep_type + asset_category = self.asset_category_1 + + purchase_date = date(2020, month=1, day=1) + depreciation_base_coeff = 0.2 + depreciation_percentage = 20.0 + depreciation_date = date(2020, month=12, day=31) + depreciated_amount = 160 + sale_price = 500 + + category_depreciation_type = asset_category.type_ids.filtered( + lambda x, dep_type=asset_dep_type: x.depreciation_type_id == dep_type + ) + category_depreciation_type.base_coeff = depreciation_base_coeff + category_depreciation_type.percentage = depreciation_percentage + category_depreciation_type.mode_id.line_ids.unlink() + + asset = self._create_asset(purchase_date) + self.assertEqual(asset.category_id, asset_category) + asset_depreciation = asset.depreciation_ids.filtered( + lambda x, dep_type=asset_dep_type: x.type_id == dep_type + ) + # pre-condition + self.assertEqual(asset_depreciation.base_coeff, depreciation_base_coeff) + self.assertEqual(asset_depreciation.percentage, depreciation_percentage) + self.assertEqual(asset_depreciation.amount_residual, 200) + + # Act: Depreciate and dismiss with sale + self._depreciate_asset(asset, depreciation_date) + self.assertEqual(asset_depreciation.amount_residual, depreciated_amount) + depreciation_lines = asset_depreciation.line_ids + sale_invoice = self._create_sale_invoice(asset, amount=sale_price) + self._link_asset_move( + sale_invoice, + "dismiss", + wiz_values={ + "asset_id": asset, + }, + ) + + # Assert: Loss is proportional to `depreciation_base_coeff` + depreciation_lines = asset_depreciation.line_ids - depreciation_lines + self.assertRecordValues( + depreciation_lines.sorted("move_type"), + [ + { + "move_type": "loss", + "amount": abs( + sale_price * depreciation_base_coeff - depreciated_amount + ), + }, + { + "move_type": "out", + "amount": sale_price * depreciation_base_coeff, + }, + ], + ) + + def test_depreciate_update_loss_coefficient(self): + """Depreciate and update an asset with a purchase invoice. + 'In' depreciation line is proportional to the depreciation coefficient.""" + # Arrange + asset_dep_type = self.civilistico_asset_dep_type + asset_category = self.asset_category_1 + + purchase_date = date(2020, month=1, day=1) + depreciation_base_coeff = 0.2 + depreciation_percentage = 20.0 + depreciation_date = date(2020, month=12, day=31) + depreciated_amount = 160 + update_date = date(2021, month=6, day=6) + update_price = 500 + + category_depreciation_type = asset_category.type_ids.filtered( + lambda x, dep_type=asset_dep_type: x.depreciation_type_id == dep_type + ) + category_depreciation_type.base_coeff = depreciation_base_coeff + category_depreciation_type.percentage = depreciation_percentage + category_depreciation_type.mode_id.line_ids.unlink() + + asset = self._create_asset(purchase_date) + self.assertEqual(asset.category_id, asset_category) + asset_depreciation = asset.depreciation_ids.filtered( + lambda x, dep_type=asset_dep_type: x.type_id == dep_type + ) + # pre-condition + self.assertEqual(asset_depreciation.base_coeff, depreciation_base_coeff) + self.assertEqual(asset_depreciation.percentage, depreciation_percentage) + self.assertEqual(asset_depreciation.amount_residual, 200) + + # Act: Depreciate and update with purchase + self._depreciate_asset(asset, depreciation_date) + self.assertEqual(asset_depreciation.amount_residual, depreciated_amount) + depreciation_lines = asset_depreciation.line_ids + purchase_invoice = self._create_purchase_invoice( + update_date, amount=update_price + ) + self._link_asset_move( + purchase_invoice, + "update", + wiz_values={ + "asset_id": asset, + }, + ) + + # Assert: 'In' is proportional to `depreciation_base_coeff` + depreciation_lines = asset_depreciation.line_ids - depreciation_lines + self.assertRecordValues( + depreciation_lines, + [ + { + "move_type": "in", + "amount": update_price * depreciation_base_coeff, + }, + ], + ) + + def test_depreciate_partial_sale_loss_coefficient(self): + """Depreciate and partial depreciate an asset with a sale invoice. + Loss is proportional to the depreciation coefficient.""" + # Arrange + asset_dep_type = self.civilistico_asset_dep_type + asset_category = self.asset_category_1 + + purchase_date = date(2020, month=1, day=1) + depreciation_base_coeff = 0.2 + depreciation_percentage = 20.0 + depreciation_date = date(2020, month=12, day=31) + depreciated_amount = 160 + sale_price = 500 + depreciated_fund_amount = 100 + asset_purchase_amount = 200 + + category_depreciation_type = asset_category.type_ids.filtered( + lambda x, dep_type=asset_dep_type: x.depreciation_type_id == dep_type + ) + category_depreciation_type.base_coeff = depreciation_base_coeff + category_depreciation_type.percentage = depreciation_percentage + category_depreciation_type.mode_id.line_ids.unlink() + + asset = self._create_asset(purchase_date) + self.assertEqual(asset.category_id, asset_category) + asset_depreciation = asset.depreciation_ids.filtered( + lambda x, dep_type=asset_dep_type: x.type_id == dep_type + ) + # pre-condition + self.assertEqual(asset_depreciation.base_coeff, depreciation_base_coeff) + self.assertEqual(asset_depreciation.percentage, depreciation_percentage) + self.assertEqual(asset_depreciation.amount_residual, 200) + + # Act: Depreciate and update with purchase + self._depreciate_asset(asset, depreciation_date) + self.assertEqual(asset_depreciation.amount_residual, depreciated_amount) + depreciation_lines = asset_depreciation.line_ids + sale_invoice = self._create_sale_invoice(asset, amount=sale_price) + self._link_asset_move( + sale_invoice, + "partial_dismiss", + wiz_values={ + "asset_id": asset, + "depreciated_fund_amount": depreciated_fund_amount, + "asset_purchase_amount": asset_purchase_amount, + }, + ) + + # Assert: Create lines are proportional to `depreciation_base_coeff` + depreciation_lines = asset_depreciation.line_ids - depreciation_lines + self.assertRecordValues( + depreciation_lines.sorted("move_type"), + [ + { + "move_type": "depreciated", + "amount": -1 * depreciated_fund_amount * depreciation_base_coeff, + }, + { + "move_type": "gain", + "amount": sale_price * depreciation_base_coeff + - (asset_purchase_amount - depreciated_fund_amount) + * depreciation_base_coeff, + }, + { + "move_type": "out", + "amount": asset_purchase_amount * depreciation_base_coeff, + }, + ], + ) diff --git a/l10n_it_asset_management/wizard/account_move_manage_asset.py b/l10n_it_asset_management/wizard/account_move_manage_asset.py index 1156aba1655..1d7af30339b 100644 --- a/l10n_it_asset_management/wizard/account_move_manage_asset.py +++ b/l10n_it_asset_management/wizard/account_move_manage_asset.py @@ -441,6 +441,9 @@ def get_dismiss_asset_vals(self): residual = dep.amount_residual dep_vals = {"line_ids": []} dep_writeoff = writeoff + base_coeff = dep.base_coeff + if base_coeff: + dep_writeoff *= base_coeff if self.dismiss_asset_without_sale and not self.move_line_ids: asset_accounting_info_ids = [ @@ -549,6 +552,14 @@ def get_partial_dismiss_asset_vals(self): else: dep_writeoff = writeoff + dep_fund_amount = fund_amt + dep_purchase_amt = purchase_amt + base_coeff = dep.base_coeff + if base_coeff: + dep_fund_amount *= base_coeff + dep_purchase_amt *= base_coeff + dep_writeoff *= base_coeff + name = _( "Partial dismissal from move(s) %(move_nums)s", move_nums=move_nums, @@ -564,7 +575,7 @@ def get_partial_dismiss_asset_vals(self): ) for line in self.move_line_ids ], - "amount": purchase_amt, + "amount": dep_purchase_amt, "date": dismiss_date, "move_type": "out", "name": name, @@ -580,7 +591,7 @@ def get_partial_dismiss_asset_vals(self): ) for line in self.move_line_ids ], - "amount": -fund_amt, + "amount": -dep_fund_amount, "date": dismiss_date, "move_type": "depreciated", "name": name, @@ -594,7 +605,7 @@ def get_partial_dismiss_asset_vals(self): ] } - balance = (fund_amt + dep_writeoff) - purchase_amt + balance = (dep_fund_amount + dep_writeoff) - dep_purchase_amt if not float_is_zero(balance, digits): loss_gain_vals = { "asset_accounting_info_ids": [ @@ -658,6 +669,10 @@ def get_update_asset_vals(self): ) for line in lines ) + base_coeff = dep.base_coeff + if base_coeff: + amount *= base_coeff + sign = 1 if float_compare(amount, 0, digits) > 0 else -1 # Block updates if the amount to be written off is higher than # the residual amount