diff --git a/l10n_it_asset_management/README.rst b/l10n_it_asset_management/README.rst index fd096a46402d..157ba5aa2a7e 100644 --- a/l10n_it_asset_management/README.rst +++ b/l10n_it_asset_management/README.rst @@ -99,8 +99,7 @@ Contributors - Nextev Srl Base icon made by `surang `__ -from -[`www.flaticon.com](https://www.flaticon.com/) `__. +from `www.flaticon.com `__. Maintainers ----------- diff --git a/l10n_it_asset_management/__manifest__.py b/l10n_it_asset_management/__manifest__.py index 518aaeaf6b5d..2e2ebc3d299d 100644 --- a/l10n_it_asset_management/__manifest__.py +++ b/l10n_it_asset_management/__manifest__.py @@ -5,7 +5,7 @@ { "name": "ITA - Gestione Cespiti", - "version": "16.0.1.0.1", + "version": "16.0.1.1.0", "category": "Localization/Italy", "summary": "Gestione Cespiti", "author": "Openforce, Odoo Community Association (OCA)", diff --git a/l10n_it_asset_management/data/asset_data.xml b/l10n_it_asset_management/data/asset_data.xml index 44305f64ea84..3eb1ed340c1a 100644 --- a/l10n_it_asset_management/data/asset_data.xml +++ b/l10n_it_asset_management/data/asset_data.xml @@ -37,8 +37,8 @@ - 1 - 1 + 1 + 1 0.5 diff --git a/l10n_it_asset_management/migrations/16.0.1.1.0/pre-migrate.py b/l10n_it_asset_management/migrations/16.0.1.1.0/pre-migrate.py new file mode 100644 index 000000000000..7ba7638b0ef0 --- /dev/null +++ b/l10n_it_asset_management/migrations/16.0.1.1.0/pre-migrate.py @@ -0,0 +1,32 @@ +# Copyright 2024 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + +MODEL_TO_RENAMED_FIELDS = { + "asset.depreciation.mode.line": [ + ("from_nr", "from_year_nr"), + ("to_nr", "to_year_nr"), + ] +} + + +def _rename_fields(env): + openupgrade.rename_fields( + env, + [ + ( + model_name, + model_name.replace(".", "_"), + field_spec[0], + field_spec[1], + ) + for model_name, field_specs in MODEL_TO_RENAMED_FIELDS.items() + for field_spec in field_specs + ], + ) + + +@openupgrade.migrate() +def migrate(env, version): + _rename_fields(env) diff --git a/l10n_it_asset_management/models/account_fiscal_year.py b/l10n_it_asset_management/models/account_fiscal_year.py index 64d697d50dce..45dd33822e28 100644 --- a/l10n_it_asset_management/models/account_fiscal_year.py +++ b/l10n_it_asset_management/models/account_fiscal_year.py @@ -35,3 +35,25 @@ def get_fiscal_year_by_date_domain(self, date, company=None): if company: domain.append(("company_id", "in", company.ids)) return domain + + @api.model + def _get_passed_years(self, start_date, end_date): + """Find all fiscal years between `start_date` and `end_date`.""" + if start_date and end_date: + overlapping_fiscal_year_domain = self.new( + { + "date_from": start_date, + "date_to": end_date, + } + )._get_overlapping_domain() + # Exclude current record's NewId + # because it is not supported in domains + overlapping_fiscal_year_domain = [ + term if term[0] != "id" else ("id", "!=", 0) + for term in overlapping_fiscal_year_domain + ] + overlapping_fiscal_years = self.search(overlapping_fiscal_year_domain) + passed_years = len(overlapping_fiscal_years) + else: + passed_years = None + return passed_years diff --git a/l10n_it_asset_management/models/asset_depreciation.py b/l10n_it_asset_management/models/asset_depreciation.py index 994d50671c25..17e085e79e90 100644 --- a/l10n_it_asset_management/models/asset_depreciation.py +++ b/l10n_it_asset_management/models/asset_depreciation.py @@ -289,26 +289,36 @@ def check_before_generate_depreciation_lines(self, dep_date): ) ) - def generate_depreciation_lines(self, dep_date): + def generate_depreciation_lines(self, dep_date, period=None, period_count=None): # Set new date within context if necessary self.check_before_generate_depreciation_lines(dep_date) new_lines = self.env["asset.depreciation.line"] for dep in self: - new_line = dep.generate_depreciation_lines_single(dep_date) + new_line = dep.generate_depreciation_lines_single( + dep_date, period=period, period_count=period_count + ) if new_line: new_lines |= new_line return new_lines - def generate_depreciation_lines_single(self, dep_date): + def generate_depreciation_lines_single( + self, dep_date, period=None, period_count=None + ): self.ensure_one() res = self.env["asset.depreciation.line"] if self.last_depreciation_date and self.last_depreciation_date > dep_date: return res - dep_nr = self.get_max_depreciation_nr() + 1 - dep = self.with_context(dep_nr=dep_nr, used_asset=self.asset_id.used) - dep_amount = dep.get_depreciation_amount(dep_date) + passed_fiscal_years = self.env["account.fiscal.year"]._get_passed_years( + self.asset_id.purchase_date, dep_date + ) + dep = self.with_context( + passed_fiscal_years=passed_fiscal_years, used_asset=self.asset_id.used + ) + dep_amount = dep.get_depreciation_amount( + dep_date, period=period, period_count=period_count + ) if not dep_amount: return res dep = dep.with_context(dep_amount=dep_amount) @@ -393,7 +403,7 @@ def get_depreciable_amount(self, dep_date=None): depreciable_amount = 0 return depreciable_amount - def get_depreciation_amount(self, dep_date): + def get_depreciation_amount(self, dep_date, period=None, period_count=None): self.ensure_one() zero_dep_date = self.zero_depreciation_until if zero_dep_date and dep_date <= zero_dep_date: @@ -401,7 +411,9 @@ def get_depreciation_amount(self, dep_date): # Get depreciable amount, multiplier and digits amount = self.get_depreciable_amount(dep_date) - multiplier = self.get_depreciation_amount_multiplier(dep_date) + multiplier = self.get_depreciation_amount_multiplier( + dep_date, period=period, period_count=period_count + ) digits = self.env["decimal.precision"].precision_get("Account") dep_amount = round(amount * multiplier, digits) @@ -411,12 +423,20 @@ def get_depreciation_amount(self, dep_date): return dep_amount - def get_depreciation_amount_multiplier(self, dep_date): + def get_depreciation_amount_multiplier( + self, dep_date, period=None, period_count=None + ): self.ensure_one() # Base multiplier multiplier = self.percentage / 100 + if period == "month": + multiplier /= 12 + + if period_count: + multiplier *= period_count + # Update multiplier from depreciation mode data multiplier *= self.mode_id.get_depreciation_amount_multiplier() @@ -503,14 +523,6 @@ def get_dismiss_account_move_vals(self): "move_type": "entry", } - def get_max_depreciation_nr(self): - self.ensure_one() - num_lines = self.line_ids.filtered("requires_depreciation_nr") - nums = num_lines.mapped("depreciation_nr") - if not nums: - nums = [0] - return max(nums) - def get_pro_rata_temporis_dates(self, date): """ Gets useful dates for pro rata temporis computations, according to diff --git a/l10n_it_asset_management/models/asset_depreciation_line.py b/l10n_it_asset_management/models/asset_depreciation_line.py index 40296df2b197..5c7634561d55 100644 --- a/l10n_it_asset_management/models/asset_depreciation_line.py +++ b/l10n_it_asset_management/models/asset_depreciation_line.py @@ -362,10 +362,14 @@ def generate_account_move_single(self): def get_account_move_vals(self): self.ensure_one() + journal = self.env.context.get( + "l10n_it_asset_override_journal", + self.asset_id.category_id.journal_id, + ) return { "company_id": self.company_id.id, "date": self.date, - "journal_id": self.asset_id.category_id.journal_id.id, + "journal_id": journal.id, "line_ids": [], "ref": _("Asset: ") + self.asset_id.make_name(), "move_type": "entry", diff --git a/l10n_it_asset_management/models/asset_depreciation_mode_line.py b/l10n_it_asset_management/models/asset_depreciation_mode_line.py index 55474f8c992d..541489c51ff1 100644 --- a/l10n_it_asset_management/models/asset_depreciation_mode_line.py +++ b/l10n_it_asset_management/models/asset_depreciation_mode_line.py @@ -9,7 +9,7 @@ class AssetDepreciationModeLine(models.Model): _name = "asset.depreciation.mode.line" _description = "Asset Depreciation Mode Line" - _order = "from_nr asc, to_nr asc" + _order = "from_year_nr asc, to_year_nr asc" application = fields.Selection( [("coefficient", "Coefficient"), ("percentage", "Percentage")], @@ -24,8 +24,12 @@ class AssetDepreciationModeLine(models.Model): "res.company", readonly=True, related="mode_id.company_id", string="Company" ) - from_nr = fields.Integer( + from_year_nr = fields.Integer( required=True, + string="From Year", + help="Minimum number of fiscal years passed " + "from asset purchase date " + "to apply this line.", ) mode_id = fields.Many2one( @@ -38,7 +42,12 @@ class AssetDepreciationModeLine(models.Model): percentage = fields.Float() - to_nr = fields.Integer() + to_year_nr = fields.Integer( + string="To Year", + help="Maximum number of fiscal years passed " + "from asset purchase date " + "to apply this line.", + ) @api.onchange("application") def onchange_application(self): @@ -53,13 +62,14 @@ def onchange_application(self): def get_depreciation_amount_multiplier(self): multiplier = 1 - nr = self._context.get("dep_nr") - if nr is None: + passed_fiscal_years = self._context.get("passed_fiscal_years") + if passed_fiscal_years is None: # Cannot compare to any line return multiplier lines = self.filtered( - lambda line: line.from_nr <= nr and (not line.to_nr or line.to_nr >= nr) + lambda line: line.from_year_nr <= passed_fiscal_years + and (not line.to_year_nr or line.to_year_nr >= passed_fiscal_years) ) if not lines: return multiplier diff --git a/l10n_it_asset_management/static/description/index.html b/l10n_it_asset_management/static/description/index.html index e8d20b8bfad4..a015e8c8172a 100644 --- a/l10n_it_asset_management/static/description/index.html +++ b/l10n_it_asset_management/static/description/index.html @@ -8,11 +8,10 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ +:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. -Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +274,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: gray; } /* line numbers */ +pre.code .ln { color: grey; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +300,7 @@ span.pre { white-space: pre } -span.problematic, pre.problematic { +span.problematic { color: red } span.section-subtitle { @@ -438,15 +437,12 @@

Contributors

  • Nextev Srl <odoo@nextev.it>
  • Base icon made by surang -from -[www.flaticon.com](https://www.flaticon.com/).

    +from www.flaticon.com.

    Maintainers

    This module is maintained by the OCA.

    - -Odoo Community Association - +Odoo Community Association

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    diff --git a/l10n_it_asset_management/tests/test_assets_management.py b/l10n_it_asset_management/tests/test_assets_management.py index a9d880e606bc..1890f9051068 100644 --- a/l10n_it_asset_management/tests/test_assets_management.py +++ b/l10n_it_asset_management/tests/test_assets_management.py @@ -176,14 +176,47 @@ def _create_asset(self, asset_date=None): ) return asset - def _depreciate_asset(self, asset, date_dep): + 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}) + .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() @@ -250,11 +283,18 @@ def _update_asset(self, entry, asset): def test_00_create_asset_depreciate_and_sale(self): today = fields.Date.today() + asset = self._create_asset(today + relativedelta(years=-1)) first_depreciation_date = today.replace(month=12, day=31) + relativedelta( years=-1 ) second_depreciation_date = today.replace(month=12, day=31) - asset = self._create_asset(today + relativedelta(years=-1)) + self._generate_fiscal_years( + asset.purchase_date, + max( + first_depreciation_date, + second_depreciation_date, + ), + ) civ_type = self.env.ref("l10n_it_asset_management.ad_type_civilistico") depreciation_id = asset.depreciation_ids.filtered( lambda x: x.type_id == civ_type @@ -513,6 +553,24 @@ def test_03_asset_from_purchase_invoice_increment(self): third_depreciation_date = today.replace(month=12, day=31) + relativedelta( years=-3 ) + # create depreciation for year -2 or -1 should do nothing as asset is totally + # depreciated + fourth_depreciation_date = today.replace(month=12, day=31) + relativedelta( + years=-2 + ) + # create depreciation for current year should depreciate totally (as computed + # value 9000*40% = 3600 is greater than residual value) + current_year_depreciation_date = today.replace(month=12, day=31) + self._generate_fiscal_years( + asset.purchase_date, + max( + first_depreciation_date, + second_depreciation_date, + third_depreciation_date, + fourth_depreciation_date, + current_year_depreciation_date, + ), + ) civ_type = self.env.ref("l10n_it_asset_management.ad_type_civilistico") depreciation_id = asset.depreciation_ids.filtered( lambda x: x.type_id == civ_type @@ -559,16 +617,8 @@ def test_03_asset_from_purchase_invoice_increment(self): ) wiz.link_asset() self.assertAlmostEqual(depreciation_id.amount_depreciable_updated, 9000) - # create depreciation for year -2 or -1 should do nothing as asset is totally - # depreciated - fourth_depreciation_date = today.replace(month=12, day=31) + relativedelta( - years=-2 - ) self._depreciate_asset(asset, fourth_depreciation_date) self.assertAlmostEqual(sum(civ_dep_lines.mapped("amount")), 7000) - # create depreciation for current year should depreciate totally (as computed - # value 9000*40% = 3600 is greater than residual value) - current_year_depreciation_date = today.replace(month=12, day=31) self._depreciate_asset(asset, current_year_depreciation_date) dep_lines = asset.depreciation_ids.line_ids civ_dep_lines = dep_lines.filtered( @@ -612,6 +662,23 @@ def test_04_asset_partial_depreciate_from_purchase_invoice_increment(self): second_depreciation_date = today.replace(month=12, day=31) + relativedelta( years=-3 ) + # create depreciation for year -4 should do nothing as asset is already + # depreciated in a later date + third_depreciation_date = today.replace(month=12, day=31) + relativedelta( + years=-4 + ) + # create depreciation for current year should depreciate totally (as computed + # value 9000*40% = 3600 is greater than residual value) + current_year_depreciation_date = today.replace(month=12, day=31) + self._generate_fiscal_years( + asset.purchase_date, + max( + first_depreciation_date, + second_depreciation_date, + third_depreciation_date, + current_year_depreciation_date, + ), + ) civ_type = self.env.ref("l10n_it_asset_management.ad_type_civilistico") depreciation_id = asset.depreciation_ids.filtered( lambda x: x.type_id == civ_type @@ -657,16 +724,8 @@ def test_04_asset_partial_depreciate_from_purchase_invoice_increment(self): ) wiz.link_asset() self.assertAlmostEqual(depreciation_id.amount_depreciable_updated, 9000) - # create depreciation for year -4 should do nothing as asset is already - # depreciated in a later date - third_depreciation_date = today.replace(month=12, day=31) + relativedelta( - years=-4 - ) self._depreciate_asset(asset, third_depreciation_date) self.assertAlmostEqual(sum(civ_dep_lines.mapped("amount")), 7000 * 0.6) - # create depreciation for current year should depreciate totally (as computed - # value 9000*40% = 3600 is greater than residual value) - current_year_depreciation_date = today.replace(month=12, day=31) self._depreciate_asset(asset, current_year_depreciation_date) dep_lines = asset.depreciation_ids.line_ids self.assertEqual(len(dep_lines), 4) @@ -756,7 +815,7 @@ def _civil_depreciate_asset(self, asset): def _generate_fiscal_years(self, start_date, end_date): fiscal_years = range( start_date.year, - end_date.year, + end_date.year + 1, ) fiscal_years_values = list() for fiscal_year in fiscal_years: @@ -816,3 +875,142 @@ def test_journal_prev_year(self): total = report.report_total_ids self.assertEqual(total.amount_depreciation_fund_curr_year, 1000) self.assertEqual(total.amount_depreciation_fund_prev_year, 1000) + + def test_monthly_depreciation(self): + """ + Monthly depreciation uses 1/12 of the coefficient + of the year the depreciation is in. + """ + # Arrange + purchase_date = date(2019, 1, 1) + asset = self._create_asset(purchase_date) + first_depreciation_date = date(2019, 1, 31) + second_depreciation_date = date(2020, 1, 31) + third_depreciation_date = date(2021, 1, 31) + self._generate_fiscal_years( + asset.purchase_date, + max( + first_depreciation_date, + second_depreciation_date, + third_depreciation_date, + ), + ) + civ_depreciation_type = self.env.ref( + "l10n_it_asset_management.ad_type_civilistico" + ) + civ_depreciation = asset.depreciation_ids.filtered( + lambda x: x.type_id == civ_depreciation_type + ) + civ_depreciation.percentage = 12.0 + depreciation_mode = asset.category_id.type_ids.mode_id + # pre-condition + self.assertEqual(asset.purchase_date, purchase_date) + self.assertEqual(civ_depreciation.amount_depreciable, 1000) + self.assertRecordValues( + depreciation_mode.line_ids, + [ + { + "from_year_nr": 1, + "to_year_nr": 1, + "application": "coefficient", + "coefficient": 0.5, + }, + ], + ) + + # Act + self._depreciate_asset(asset, first_depreciation_date, period="month") + self._depreciate_asset(asset, second_depreciation_date, period="month") + self._depreciate_asset( + asset, third_depreciation_date, period="month", period_count=2 + ) + + # Assert + self.assertRecordValues( + civ_depreciation.line_ids, + [ + { + "date": first_depreciation_date, + "amount": 5, + }, + { + "date": second_depreciation_date, + "amount": 10, + }, + { + "date": third_depreciation_date, + "amount": 20, + }, + ], + ) + + def test_missing_fiscal_year_warning(self): + """ + If some years are not configured as fiscal years, + the wizard shows a warning. + """ + # Arrange + purchase_date = date(2019, 1, 1) + asset = self._create_asset(purchase_date) + depreciation_date = date(2020, 1, 1) + + # Act + depreciate_wizard = self._depreciate_asset_wizard(asset, depreciation_date) + + # Assert 1: some fiscal years are missing + self.assertTrue(depreciate_wizard.missing_fiscal_year_warning) + + # Act 2: Generate missing years + self._generate_fiscal_years( + asset.purchase_date, + depreciation_date, + ) + + # Assert 2: no fiscal years are missing + depreciate_wizard = self._depreciate_asset_wizard(asset, depreciation_date) + self.assertFalse(depreciate_wizard.missing_fiscal_year_warning) + + def test_override_journal(self): + """ + Set an "Override Journal" in the depreciation wizard, + the journal entries are created in the selected journal. + """ + # Arrange + override_journal = self.env["account.journal"].create( + { + "name": "Test override journal", + "code": "TOJ", + "type": "general", + } + ) + purchase_date = date(2019, 1, 1) + asset = self._create_asset(purchase_date) + depreciation_date = date(2019, 1, 31) + self._generate_fiscal_years( + asset.purchase_date, + depreciation_date, + ) + civ_depreciation_type = self.env.ref( + "l10n_it_asset_management.ad_type_civilistico" + ) + civ_depreciation = asset.depreciation_ids.filtered( + lambda x: x.type_id == civ_depreciation_type + ) + civ_depreciation.percentage = 12.0 + depreciate_asset_wizard = self._depreciate_asset_wizard( + asset, + depreciation_date, + period="month", + override_journal=override_journal, + ) + # pre-condition + self.assertNotEqual( + depreciate_asset_wizard.journal_id, asset.category_id.journal_id + ) + + # Act + depreciate_asset_wizard.do_generate() + + # Assert + account_move = asset.depreciation_ids.line_ids.move_id + self.assertEqual(account_move.journal_id, depreciate_asset_wizard.journal_id) diff --git a/l10n_it_asset_management/views/asset_depreciation_mode.xml b/l10n_it_asset_management/views/asset_depreciation_mode.xml index 38bb73645c8e..b9100c8a3039 100644 --- a/l10n_it_asset_management/views/asset_depreciation_mode.xml +++ b/l10n_it_asset_management/views/asset_depreciation_mode.xml @@ -47,8 +47,8 @@ > - - + + + +