diff --git a/CHANGELOG b/CHANGELOG index 7405999c75..b71bb1f0de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add asset depreciation report + Version 3.4.0 - 2014-10-20 * Bug fixes (see mercurial logs for details) diff --git a/INSTALL b/INSTALL index 5220d7a5b1..600bd53ad7 100644 --- a/INSTALL +++ b/INSTALL @@ -5,6 +5,8 @@ Prerequisites ------------- * Python 2.7 or later (http://www.python.org/) + * python-dateutil (http://labix.org/python-dateutil) + * cached-property (https://github.com/pydanny/cached-property) * trytond (http://www.tryton.org/) * trytond_account (http://www.tryton.org/) * trytond_account_product (http://www.tryton.org/) diff --git a/__init__.py b/__init__.py index 8025921e83..37d58889e7 100644 --- a/__init__.py +++ b/__init__.py @@ -16,6 +16,7 @@ def register(): CreateMovesStart, UpdateAssetStart, UpdateAssetShowDepreciation, + PrintDepreciationTableStart, Category, Template, InvoiceLine, @@ -26,4 +27,8 @@ def register(): Pool.register( CreateMoves, UpdateAsset, + PrintDepreciationTable, module='account_asset', type_='wizard') + Pool.register( + AssetDepreciationTable, + module='account_asset', type_='report') diff --git a/asset.py b/asset.py index 01deb6e733..a7a96b05bf 100644 --- a/asset.py +++ b/asset.py @@ -5,17 +5,23 @@ import calendar from decimal import Decimal from dateutil import relativedelta from dateutil import rrule +from itertools import groupby +from cached_property import cached_property from trytond.model import Workflow, ModelSQL, ModelView, fields from trytond.pyson import Eval, Bool, If from trytond.pool import Pool from trytond.transaction import Transaction -from trytond.wizard import Wizard, StateView, StateTransition, Button +from trytond.wizard import (Wizard, StateView, StateTransition, StateAction, + Button) from trytond.tools import grouped_slice +from trytond.modules.company import CompanyReport __all__ = ['Asset', 'AssetLine', 'AssetUpdateMove', 'CreateMovesStart', 'CreateMoves', - 'UpdateAssetStart', 'UpdateAssetShowDepreciation', 'UpdateAsset'] + 'UpdateAssetStart', 'UpdateAssetShowDepreciation', 'UpdateAsset', + 'AssetDepreciationTable', + 'PrintDepreciationTableStart', 'PrintDepreciationTable'] def date2datetime(date): @@ -811,3 +817,198 @@ class UpdateAsset(Wizard): }) Asset.create_lines([asset]) return 'end' + + +class AssetDepreciationTable(CompanyReport): + 'Asset Depreciation Table' + __name__ = 'account.asset.depreciation_table' + + @classmethod + def get_context(cls, records, data): + context = super(AssetDepreciationTable, cls).get_context(records, data) + + AssetDepreciation = cls.get_asset_depreciation() + AssetDepreciation.start_date = data['start_date'] + AssetDepreciation.end_date = data['end_date'] + Grouper = cls.get_grouper() + grouped_assets = groupby(sorted(records, key=cls.group_assets), + cls.group_assets) + context['grouped_depreciations'] = grouped_depreciations = [] + for g_key, assets in grouped_assets: + depreciations = [AssetDepreciation(a) for a in assets] + grouped_depreciations.append(Grouper(g_key, depreciations)) + + return context + + @staticmethod + def group_assets(asset): + return asset.product + + @classmethod + def get_grouper(cls): + + class Grouper(object): + def __init__(self, key, depreciations): + self.product = key + self.depreciations = depreciations + for attr_name in self.grouped_attributes(): + setattr(self, attr_name, + cached_property(self.adder(attr_name))) + + def adder(self, attr_name): + def _sum(self): + return sum(getattr(d, attr_name) + for d in self.depreciations if getattr(d, attr_name)) + return _sum + + @staticmethod + def grouped_attributes(): + return { + 'start_fixed_value', + 'value_increase', + 'value_decrease', + 'end_fixed_value', + 'start_value', + 'amortization_increase', + 'amortization_decrease', + 'end_value', + 'actual_value', + 'closing_value', + } + + return Grouper + + @classmethod + def get_asset_depreciation(cls): + + class AssetDepreciation(object): + def __init__(self, asset): + self.asset = asset + + @cached_property + def asset_lines(self): + return filter( + lambda l: self.start_date < l.date <= self.end_date, + self.asset.lines) + + @cached_property + def update_lines(self): + filter_ = lambda l: (l.account.kind == 'other' + and self.start_date < l.move.date <= self.end_date) + return filter(filter_, + (l for m in self.asset.update_moves for l in m.lines)) + + @cached_property + def start_fixed_value(self): + if self.start_date < self.asset.start_date: + return 0 + value = self.asset_lines[0].acquired_value + date = self.asset_lines[0].date + for line in self.update_lines: + if line.move.date < date: + value += line.debit - line.credit + return value + + @cached_property + def value_increase(self): + value = sum(l.debit - l.credit for l in self.update_lines + if l.debit > l.credit) + if self.start_date < self.asset.start_date: + value += self.asset_lines[0].acquired_value + return value + + @cached_property + def value_decrease(self): + return sum(l.credit - l.debit for l in self.update_lines + if l.credit > l.debit) + + @cached_property + def end_fixed_value(self): + value = self.asset_lines[-1].acquired_value + date = self.asset_lines[-1].date + for line in self.update_lines: + if line.move.date > date: + value += line.debit - line.credit + return value + + @cached_property + def start_value(self): + return (self.asset_lines[0].actual_value + + self.asset_lines[0].depreciation) + + @cached_property + def amortization_increase(self): + return sum(l.depreciation for l in self.asset_lines + if l.depreciation > 0) + + @cached_property + def amortization_decrease(self): + return sum(l.depreciation for l in self.asset_lines + if l.depreciation < 0) + + @cached_property + def end_value(self): + return self.asset_lines[-1].actual_value + + @cached_property + def actual_value(self): + value = self.end_value + date = self.asset_lines[-1].date + value += sum(l.debit - l.credit for l in self.update_lines + if l.move.date > date) + return value + + @cached_property + def closing_value(self): + if not self.asset.move: + return None + revenue_lines = [l for l in self.asset.move.lines + if l.account == self.asset.product.account_revenue_used] + return sum(l.debit - l.credit for l in revenue_lines) + + return AssetDepreciation + + +class PrintDepreciationTableStart(ModelView): + 'Asset Depreciation Table Start' + __name__ = 'account.asset.print_depreciation_table.start' + + start_date = fields.Date('Start Date', required=True, + domain=[('start_date', '<', Eval('end_date'))], + depends=['end_date']) + end_date = fields.Date('End Date', required=True, + domain=[('end_date', '>', Eval('start_date'))], + depends=['start_date']) + + @staticmethod + def default_start_date(): + return datetime.date.today() - relativedelta.relativedelta(years=1) + + @staticmethod + def default_end_date(): + return datetime.date.today() + + +class PrintDepreciationTable(Wizard): + 'Asset Depreciation Table' + __name__ = 'account.asset.print_depreciation_table' + start = StateView('account.asset.print_depreciation_table.start', + 'account_asset.print_depreciation_table_start_view_form', [ + Button('Cancel', 'end', 'tryton-cancel'), + Button('Print', 'print_', 'tryton-print', default=True), + ]) + print_ = StateAction('account_asset.report_depreciation_table') + + def do_print_(self, action): + pool = Pool() + Asset = pool.get('account.asset') + assets = Asset.search([ + ('start_date', '<', self.start.end_date), + ('end_date', '>', self.start.start_date), + ('state', '=', 'running'), + ]) + return action, { + 'ids': [a.id for a in assets], + 'start_date': self.start.start_date, + 'end_date': self.start.end_date, + } diff --git a/asset.xml b/asset.xml index f9dfcd0a69..940a4092af 100644 --- a/asset.xml +++ b/asset.xml @@ -185,6 +185,27 @@ + + + Depreciation Table + account.asset + account.asset.depreciation_table + account_asset/asset_table.odt + + + account.asset.print_depreciation_table.start + form + print_depreciation_table_start + + + Print Depreciation Table + account.asset.print_depreciation_table + + diff --git a/asset_table.odt b/asset_table.odt new file mode 100644 index 0000000000..d09dfd438d Binary files /dev/null and b/asset_table.odt differ diff --git a/setup.py b/setup.py index ad46170f78..8d34c844fd 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ if minor_version % 2: 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % ( name[8:], name, version)) -requires = [] +requires = ['cached-property', 'python-dateutil'] for dep in info.get('depends', []): if not re.match(r'(ir|res|webdav)(\W|$)', dep): requires.append(get_require_version('trytond_%s' % dep)) diff --git a/tests/scenario_account_asset.rst b/tests/scenario_account_asset.rst index 6b3c418811..86c45b27e9 100644 --- a/tests/scenario_account_asset.rst +++ b/tests/scenario_account_asset.rst @@ -258,3 +258,9 @@ Sale the asset:: Decimal('339.28') >>> depreciation_account.credit Decimal('239.28') + +Generate the asset report:: + + >>> print_depreciation_table = Wizard( + ... 'account.asset.print_depreciation_table') + >>> print_depreciation_table.execute('print_') diff --git a/view/print_depreciation_table_start.xml b/view/print_depreciation_table_start.xml new file mode 100644 index 0000000000..11c0b79dd1 --- /dev/null +++ b/view/print_depreciation_table_start.xml @@ -0,0 +1,9 @@ + + +
+