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 @@
+
+
+