diff --git a/l10n_it_asset_management/__manifest__.py b/l10n_it_asset_management/__manifest__.py
index 12aa1da94dc9..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.0",
+ "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..98c9d4ad5a9e 100644
--- a/l10n_it_asset_management/models/asset_depreciation.py
+++ b/l10n_it_asset_management/models/asset_depreciation.py
@@ -289,26 +289,30 @@ 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):
# 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)
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):
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)
if not dep_amount:
return res
dep = dep.with_context(dep_amount=dep_amount)
@@ -393,7 +397,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):
self.ensure_one()
zero_dep_date = self.zero_depreciation_until
if zero_dep_date and dep_date <= zero_dep_date:
@@ -401,7 +405,7 @@ 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)
digits = self.env["decimal.precision"].precision_get("Account")
dep_amount = round(amount * multiplier, digits)
@@ -411,12 +415,15 @@ 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):
self.ensure_one()
# Base multiplier
multiplier = self.percentage / 100
+ if period == "month":
+ multiplier /= 12
+
# Update multiplier from depreciation mode data
multiplier *= self.mode_id.get_depreciation_amount_multiplier()
@@ -503,14 +510,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_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/tests/test_assets_management.py b/l10n_it_asset_management/tests/test_assets_management.py
index 4a62c4bb4a1b..9c54de7934d8 100644
--- a/l10n_it_asset_management/tests/test_assets_management.py
+++ b/l10n_it_asset_management/tests/test_assets_management.py
@@ -167,15 +167,24 @@ def _create_asset(self, asset_date):
)
return asset
- def _depreciate_asset(self, asset, date_dep):
+ def _depreciate_asset_wizard(self, asset, date_dep, period="year"):
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,
+ }
+ )
)
+ return wiz
+
+ def _depreciate_asset(self, asset, date_dep, period="year"):
+ wiz = self._depreciate_asset_wizard(asset, date_dep, period)
wiz.do_generate()
def _create_purchase_invoice(self, invoice_date, tax_ids=False, amount=7000):
@@ -212,11 +221,18 @@ def _create_purchase_invoice(self, invoice_date, tax_ids=False, amount=7000):
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
@@ -475,6 +491,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
@@ -521,16 +555,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(
@@ -574,6 +600,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
@@ -619,16 +662,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)
@@ -678,7 +713,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:
@@ -738,3 +773,88 @@ 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)
+ self._generate_fiscal_years(
+ asset.purchase_date,
+ max(
+ first_depreciation_date,
+ second_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")
+
+ # Assert
+ self.assertRecordValues(
+ civ_depreciation.line_ids,
+ [
+ {
+ "date": first_depreciation_date,
+ "amount": 5,
+ },
+ {
+ "date": second_depreciation_date,
+ "amount": 10,
+ },
+ ],
+ )
+
+ 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)
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 @@
>
-
-
+
+
+
+
+
+
+