diff --git a/CHANGELOG b/CHANGELOG index 82b17fa36b..7ba3991bff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Use fixed year of 365 days + Version 5.4.0 - 2019-11-04 * Bug fixes (see mercurial logs for details) * Add depreciated amount diff --git a/asset.py b/asset.py index 0459560338..44e391ae1c 100644 --- a/asset.py +++ b/asset.py @@ -30,6 +30,26 @@ def date2datetime(date): return datetime.datetime.combine(date, datetime.time()) +February = 2 + + +def normalized_delta(start, end): + "Returns timedelta using fixed 365 days per year" + assert start <= end + delta = end - start + correction = 0 + if start.year == end.year: + if calendar.isleap(start.year) and start.month <= 2 and end.month > 2: + correction -= 1 + else: + if calendar.isleap(start.year) and start.month <= February: + correction -= 1 + if calendar.isleap(end.year) and end.month > February: + correction -= 1 + correction -= calendar.leapdays(start.year + 1, end.year) + return delta + datetime.timedelta(days=correction) + + class Asset(Workflow, ModelSQL, ModelView): 'Asset' __name__ = 'account.asset' @@ -396,21 +416,30 @@ class Asset(Workflow, ModelSQL, ModelView): start_date = max([self.start_date - relativedelta.relativedelta(days=1)] + [l.date for l in self.lines]) - first_delta = dates[0] - start_date + first_delta = normalized_delta(start_date, dates[0]) if len(dates) > 1: - last_delta = dates[-1] - dates[-2] + last_delta = normalized_delta(dates[-2], dates[-1]) else: last_delta = first_delta if self.frequency == 'monthly': _, first_ndays = calendar.monthrange( dates[0].year, dates[0].month) + if (calendar.isleap(dates[0].year) + and dates[0].month == February): + first_ndays -= 1 _, last_ndays = calendar.monthrange( dates[-1].year, dates[-1].month) + if (calendar.isleap(dates[-1].year) + and dates[-1].month == February): + last_ndays -= 1 elif self.frequency == 'yearly': - first_ndays = 365 + calendar.isleap(dates[0].year) - last_ndays = 365 + calendar.isleap(dates[-1].year) - first_ratio = Decimal(first_delta.days) / Decimal(first_ndays) - last_ratio = Decimal(last_delta.days) / Decimal(last_ndays) + first_ndays = last_ndays = 365 + first_ratio = ( + Decimal(min(first_delta.days, first_ndays)) + / Decimal(first_ndays)) + last_ratio = ( + Decimal(min(last_delta.days, last_ndays)) + / Decimal(last_ndays)) depreciation = amount / ( len(dates) - 2 + first_ratio + last_ratio) if date == dates[0]: diff --git a/doc/index.rst b/doc/index.rst index a8c9ed03b0..a46afd95f7 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -13,10 +13,14 @@ An Asset defines how an asset is depreciated. It is mainly defined by: - Value, Depreciated Amount and Residual Value. - Start and End Date. - Depreciation Method: + - Linear + - Frequency: + - Monthly - - Yearly + - Yearly (using fixed year of 365 days) + - Lines. The asset can be in one of this states: @@ -31,7 +35,7 @@ The asset can be in one of this states: * Closed - The value of the asset has been completly depreciated. + The value of the asset has been completely depreciated. A wizard "Create Assets Moves" allows to post all accounting move up to a date. diff --git a/tests/test_account_asset.py b/tests/test_account_asset.py index 97ea9f9d5e..63e3758d69 100644 --- a/tests/test_account_asset.py +++ b/tests/test_account_asset.py @@ -1,5 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +import datetime as dt import unittest import doctest import trytond.tests.test_tryton @@ -7,12 +8,40 @@ from trytond.tests.test_tryton import ModuleTestCase from trytond.tests.test_tryton import doctest_teardown from trytond.tests.test_tryton import doctest_checker +from trytond.modules.account_asset.asset import normalized_delta + class AccountAssetTestCase(ModuleTestCase): 'Test AccountAsset module' module = 'account_asset' extras = ['purchase'] + def test_normalized_delta(self): + "Test normalized detal" + for start, end, delta in [ + (dt.date(2019, 1, 1), dt.date(2019, 12, 31), + dt.timedelta(days=364)), + (dt.date(2019, 1, 1), dt.date(2020, 1, 1), + dt.timedelta(days=365)), + (dt.date(2019, 1, 1), dt.date(2019, 3, 1), + dt.timedelta(days=31 + 28)), + (dt.date(2024, 1, 1), dt.date(2024, 2, 1), + dt.timedelta(days=31)), + (dt.date(2024, 1, 1), dt.date(2024, 3, 1), + dt.timedelta(days=31 + 28)), + (dt.date(2024, 3, 1), dt.date(2024, 4, 1), + dt.timedelta(days=31)), + (dt.date(2024, 1, 1), dt.date(2025, 1, 1), + dt.timedelta(days=365)), + (dt.date(2023, 1, 1), dt.date(2025, 1, 1), + dt.timedelta(days=365 * 2)), + (dt.date(2000, 1, 1), dt.date(2020, 1, 1), + dt.timedelta(days=365 * 20)), + ]: + self.assertEqual( + normalized_delta(start, end), delta, + msg='%s - %s' % (start, end)) + def suite(): suite = trytond.tests.test_tryton.suite()