diff --git a/account_asset_management/__manifest__.py b/account_asset_management/__manifest__.py
index 84c4e1bf4..61b841197 100644
--- a/account_asset_management/__manifest__.py
+++ b/account_asset_management/__manifest__.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Noviat
+# Copyright 2009-2019 Noviat
# Copyright 2019 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -8,6 +8,7 @@
'license': 'AGPL-3',
'depends': [
'account',
+ 'report_xlsx_helper',
],
'excludes': ['account_asset'],
'author': "Noviat,Odoo Community Association (OCA)",
@@ -29,5 +30,6 @@
'views/account_move.xml',
'views/account_move_line.xml',
'views/menuitem.xml',
+ 'wizard/wiz_account_asset_report.xml',
],
}
diff --git a/account_asset_management/models/account_asset.py b/account_asset_management/models/account_asset.py
index a8acfc4ba..a052141f8 100644
--- a/account_asset_management/models/account_asset.py
+++ b/account_asset_management/models/account_asset.py
@@ -1064,3 +1064,68 @@ class AccountAsset(models.Model):
triggers.sudo().write(recompute_vals)
return (result, error_log)
+
+ @api.model
+ def _xls_acquisition_fields(self):
+ """
+ Update list in custom module to add/drop columns or change order
+ """
+ return [
+ 'account', 'name', 'code', 'date_start', 'depreciation_base',
+ 'salvage_value',
+ ]
+
+ @api.model
+ def _xls_active_fields(self):
+ """
+ Update list in custom module to add/drop columns or change order
+ """
+ return [
+ 'account', 'name', 'code', 'date_start',
+ 'depreciation_base', 'salvage_value',
+ 'period_start_value', 'period_depr', 'period_end_value',
+ 'period_end_depr',
+ 'method', 'method_number', 'prorata', 'state',
+ ]
+
+ @api.model
+ def _xls_removal_fields(self):
+ """
+ Update list in custom module to add/drop columns or change order
+ """
+ return [
+ 'account', 'name', 'code', 'date_remove', 'depreciation_base',
+ 'salvage_value',
+ ]
+
+ @api.model
+ def _xls_asset_template(self):
+ """
+ Template updates
+
+ """
+ return {}
+
+ @api.model
+ def _xls_acquisition_template(self):
+ """
+ Template updates
+
+ """
+ return {}
+
+ @api.model
+ def _xls_active_template(self):
+ """
+ Template updates
+
+ """
+ return {}
+
+ @api.model
+ def _xls_removal_template(self):
+ """
+ Template updates
+
+ """
+ return {}
diff --git a/account_asset_management/models/account_asset_group.py b/account_asset_management/models/account_asset_group.py
index d05870ce1..84d49ddb3 100644
--- a/account_asset_management/models/account_asset_group.py
+++ b/account_asset_management/models/account_asset_group.py
@@ -1,14 +1,15 @@
-# Copyright 2009-2018 Noviat
+# Copyright 2009-2020 Noviat
# Copyright 2019 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
+from odoo.osv import expression
class AccountAssetGroup(models.Model):
_name = 'account.asset.group'
_description = 'Asset Group'
- _order = 'name'
+ _order = 'code, name'
_parent_store = True
name = fields.Char(string='Name', size=64, required=True, index=True)
@@ -23,7 +24,52 @@ class AccountAssetGroup(models.Model):
comodel_name='account.asset.group',
string='Parent Asset Group',
ondelete='restrict')
+ child_ids = fields.One2many(
+ comodel_name='account.asset.group',
+ inverse_name='parent_id',
+ string='Child Asset Groups')
@api.model
def _default_company_id(self):
return self.env['res.company']._company_default_get('account.asset')
+
+ @api.multi
+ def name_get(self):
+ result = []
+ params = self.env.context.get('params')
+ list_view = params and params.get('view_type') == 'list'
+ short_name_len = 16
+ for rec in self:
+ if rec.code:
+ full_name = rec.code + ' ' + rec.name
+ short_name = rec.code
+ else:
+ full_name = rec.name
+ if len(full_name) > short_name_len:
+ short_name = full_name[:16] + '...'
+ else:
+ short_name = full_name
+ if list_view:
+ name = short_name
+ else:
+ name = full_name
+ result.append((rec.id, name))
+ return result
+
+ @api.model
+ def _name_search(self, name, args=None, operator='ilike', limit=100,
+ name_get_uid=None):
+ args = args or []
+ domain = []
+ if name:
+ domain = [
+ '|',
+ ('code', '=ilike', name.split(' ')[0] + '%'),
+ ('name', operator, name)
+ ]
+ if operator in expression.NEGATIVE_TERM_OPERATORS:
+ domain = ['&', '!'] + domain[1:]
+ rec_ids = self._search(
+ expression.AND([domain, args]), limit=limit,
+ access_rights_uid=name_get_uid)
+ return self.browse(rec_ids).name_get()
diff --git a/account_asset_management/readme/DESCRIPTION.rst b/account_asset_management/readme/DESCRIPTION.rst
index 403e99d48..1e7426e14 100644
--- a/account_asset_management/readme/DESCRIPTION.rst
+++ b/account_asset_management/readme/DESCRIPTION.rst
@@ -7,7 +7,5 @@ The full asset life-cycle is managed (from asset creation to asset removal).
Assets can be created manually as well as automatically
(via the creation of an accounting entry on the asset account).
-Excel based reporting is available via the 'account_asset_management_xls' module.
-
The module contains a large number of functional enhancements compared to
the standard account_asset module from Odoo.
diff --git a/account_asset_management/report/__init__.py b/account_asset_management/report/__init__.py
index d73a2f553..e5f30c662 100644
--- a/account_asset_management/report/__init__.py
+++ b/account_asset_management/report/__init__.py
@@ -1,3 +1,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import account_asset_report
+from . import account_asset_report_xls
diff --git a/account_asset_management/report/account_asset_report_xls.py b/account_asset_management/report/account_asset_report_xls.py
new file mode 100644
index 000000000..a83037984
--- /dev/null
+++ b/account_asset_management/report/account_asset_report_xls.py
@@ -0,0 +1,673 @@
+# Copyright 2009-2019 Noviat
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+import logging
+
+from odoo import models, _
+from odoo.exceptions import UserError
+from odoo.tools.translate import translate
+
+_logger = logging.getLogger(__name__)
+
+
+IR_TRANSLATION_NAME = 'account.asset.report'
+
+
+class AssetReportXlsx(models.AbstractModel):
+ _name = 'report.account_asset_management.asset_report_xls'
+ _inherit = 'report.report_xlsx.abstract'
+
+ def _(self, src):
+ lang = self.env.context.get('lang', 'en_US')
+ val = translate(
+ self.env.cr, IR_TRANSLATION_NAME, 'report', lang, src) or src
+ return val
+
+ def _get_ws_params(self, wb, data, wiz):
+ self._grouped_assets = self._get_assets(wiz)
+ s1 = self._get_acquisition_ws_params(wb, data, wiz)
+ s2 = self._get_active_ws_params(wb, data, wiz)
+ s3 = self._get_removal_ws_params(wb, data, wiz)
+ return [s1, s2, s3]
+
+ def _get_asset_template(self):
+
+ asset_template = {
+ 'account': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Account'),
+ },
+ 'asset': {
+ 'type': 'string',
+ 'value': self._render(
+ "asset.profile_id.account_asset_id.code"),
+ },
+ 'totals': {
+ 'type': 'string',
+ 'value': self._('Totals'),
+ },
+ 'width': 20,
+ },
+ 'name': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Name'),
+ },
+ 'asset_group': {
+ 'type': 'string',
+ 'value': self._render("group.name or ''"),
+ },
+ 'asset': {
+ 'type': 'string',
+ 'value': self._render("asset.name"),
+ },
+ 'width': 40,
+ },
+ 'code': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Reference'),
+ },
+ 'asset_group': {
+ 'type': 'string',
+ 'value': self._render("group.code or ''"),
+ },
+ 'asset': {
+ 'type': 'string',
+ 'value': self._render("asset.code or ''"),
+ },
+ 'width': 20,
+ },
+ 'date_start': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Asset Start Date'),
+ },
+ 'asset': {
+ 'value': self._render("asset.date_start"),
+ 'format': self.format_tcell_date_left,
+ },
+ 'width': 20,
+ },
+ 'date_remove': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Asset Removal Date'),
+ },
+ 'asset': {
+ 'value': self._render("asset.date_remove"),
+ 'format': self.format_tcell_date_left,
+ },
+ 'width': 20,
+ },
+ 'depreciation_base': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Depreciation Base'),
+ 'format': self.format_theader_yellow_right,
+ },
+ 'asset_group': {
+ 'type': 'number',
+ 'value': self._render("group._depreciation_base"),
+ 'format': self.format_theader_blue_amount_right,
+ },
+ 'asset': {
+ 'type': 'number',
+ 'value': self._render("asset.depreciation_base"),
+ 'format': self.format_tcell_amount_right,
+ },
+ 'totals': {
+ 'type': 'formula',
+ 'value': self._render('asset_total_formula'),
+ 'format': self.format_theader_yellow_amount_right,
+ },
+ 'width': 18,
+ },
+ 'salvage_value': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Salvage Value'),
+ 'format': self.format_theader_yellow_right,
+ },
+ 'asset_group': {
+ 'type': 'number',
+ 'value': self._render("group._salvage_value"),
+ 'format': self.format_theader_blue_amount_right,
+ },
+ 'asset': {
+ 'type': 'number',
+ 'value': self._render("asset.salvage_value"),
+ 'format': self.format_tcell_amount_right,
+ },
+ 'totals': {
+ 'type': 'formula',
+ 'value': self._render('salvage_total_formula'),
+ 'format': self.format_theader_yellow_amount_right,
+ },
+ 'width': 18,
+ },
+ 'period_start_value': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Period Start Value'),
+ 'format': self.format_theader_yellow_right,
+ },
+ 'asset_group': {
+ 'type': 'number',
+ 'value': self._render("group._period_start_value"),
+ 'format': self.format_theader_blue_amount_right,
+ },
+ 'asset': {
+ 'type': 'number',
+ 'value': self._render("asset._period_start_value"),
+ 'format': self.format_tcell_amount_right,
+ },
+ 'totals': {
+ 'type': 'formula',
+ 'value': self._render('period_start_total_formula'),
+ 'format': self.format_theader_yellow_amount_right,
+ },
+ 'width': 18,
+ },
+ 'period_depr': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Period Depreciation'),
+ 'format': self.format_theader_yellow_right,
+ },
+ 'asset_group': {
+ 'type': 'formula',
+ 'value': self._render("period_diff_formula"),
+ 'format': self.format_theader_blue_amount_right,
+ },
+ 'asset': {
+ 'type': 'formula',
+ 'value': self._render("period_diff_formula"),
+ 'format': self.format_tcell_amount_right,
+ },
+ 'totals': {
+ 'type': 'formula',
+ 'value': self._render('period_diff_formula'),
+ 'format': self.format_theader_yellow_amount_right,
+ },
+ 'width': 18,
+ },
+ 'period_end_value': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Period End Value'),
+ 'format': self.format_theader_yellow_right,
+ },
+ 'asset_group': {
+ 'type': 'number',
+ 'value': self._render("group._period_end_value"),
+ 'format': self.format_theader_blue_amount_right,
+ },
+ 'asset': {
+ 'type': 'number',
+ 'value': self._render("asset._period_end_value"),
+ 'format': self.format_tcell_amount_right,
+ },
+ 'totals': {
+ 'type': 'formula',
+ 'value': self._render('period_end_total_formula'),
+ 'format': self.format_theader_yellow_amount_right,
+ },
+ 'width': 18,
+ },
+ 'period_end_depr': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Tot. Depreciation'),
+ 'format': self.format_theader_yellow_right,
+ },
+ 'asset_group': {
+ 'type': 'formula',
+ 'value': self._render("total_depr_formula"),
+ 'format': self.format_theader_blue_amount_right,
+ },
+ 'asset': {
+ 'type': 'formula',
+ 'value': self._render("total_depr_formula"),
+ 'format': self.format_tcell_amount_right,
+ },
+ 'totals': {
+ 'type': 'formula',
+ 'value': self._render('total_depr_formula'),
+ 'format': self.format_theader_yellow_amount_right,
+ },
+ 'width': 18,
+ },
+ 'method': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Comput. Method'),
+ 'format': self.format_theader_yellow_center,
+ },
+ 'asset': {
+ 'type': 'string',
+ 'value': self._render("asset.method or ''"),
+ 'format': self.format_tcell_center,
+ },
+ 'width': 20,
+ },
+ 'method_number': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Number of Years'),
+ 'format': self.format_theader_yellow_center,
+ },
+ 'asset': {
+ 'type': 'number',
+ 'value': self._render("asset.method_number"),
+ 'format': self.format_tcell_integer_center,
+ },
+ 'width': 20,
+ },
+ 'prorata': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Prorata Temporis'),
+ 'format': self.format_theader_yellow_center,
+ },
+ 'asset': {
+ 'type': 'boolean',
+ 'value': self._render("asset.prorata"),
+ 'format': self.format_tcell_center,
+ },
+ 'width': 20,
+ },
+ 'state': {
+ 'header': {
+ 'type': 'string',
+ 'value': self._('Status'),
+ 'format': self.format_theader_yellow_center,
+ },
+ 'asset': {
+ 'type': 'string',
+ 'value': self._render("asset.state"),
+ 'format': self.format_tcell_center,
+ },
+ 'width': 8,
+ },
+ }
+ asset_template.update(
+ self.env['account.asset']._xls_asset_template())
+
+ return asset_template
+
+ def _get_acquisition_ws_params(self, wb, data, wiz):
+
+ acquisition_template = self._get_asset_template()
+ acquisition_template.update(
+ self.env['account.asset']._xls_acquisition_template())
+ wl_acq = self.env['account.asset']._xls_acquisition_fields()
+ title = self._get_title(wiz, 'acquisition', format='normal')
+ title_short = self._get_title(wiz, 'acquisition', format='short')
+ sheet_name = title_short[:31].replace('/', '-')
+
+ return {
+ 'ws_name': sheet_name,
+ 'generate_ws_method': '_asset_report',
+ 'title': title,
+ 'wanted_list': wl_acq,
+ 'col_specs': acquisition_template,
+ 'report_type': 'acquisition',
+ }
+
+ def _get_active_ws_params(self, wb, data, wiz):
+
+ active_template = self._get_asset_template()
+ active_template.update(
+ self.env['account.asset']._xls_active_template())
+ wl_act = self.env['account.asset']._xls_active_fields()
+ title = self._get_title(wiz, 'active', format='normal')
+ title_short = self._get_title(wiz, 'active', format='short')
+ sheet_name = title_short[:31].replace('/', '-')
+
+ return {
+ 'ws_name': sheet_name,
+ 'generate_ws_method': '_asset_report',
+ 'title': title,
+ 'wanted_list': wl_act,
+ 'col_specs': active_template,
+ 'report_type': 'active',
+ }
+
+ def _get_removal_ws_params(self, wb, data, wiz):
+
+ removal_template = self._get_asset_template()
+ removal_template.update(
+ self.env['account.asset']._xls_removal_template())
+ wl_dsp = self.env['account.asset']._xls_removal_fields()
+ title = self._get_title(wiz, 'removal', format='normal')
+ title_short = self._get_title(wiz, 'removal', format='short')
+ sheet_name = title_short[:31].replace('/', '-')
+
+ return {
+ 'ws_name': sheet_name,
+ 'generate_ws_method': '_asset_report',
+ 'title': title,
+ 'wanted_list': wl_dsp,
+ 'col_specs': removal_template,
+ 'report_type': 'removal',
+ }
+
+ def _get_title(self, wiz, report, format='normal'):
+
+ prefix = '{} - {}'.format(wiz.date_from, wiz.date_to)
+ if report == 'acquisition':
+ if format == 'normal':
+ title = prefix + ' : ' + _('New Acquisitions')
+ else:
+ title = 'ACQ'
+ elif report == 'active':
+ if format == 'normal':
+ title = prefix + ' : ' + _('Active Assets')
+ else:
+ title = 'ACT'
+ else:
+ if format == 'normal':
+ title = prefix + ' : ' + _('Removed Assets')
+ else:
+ title = 'DSP'
+ return title
+
+ def _report_title(self, ws, row_pos, ws_params, data, wiz):
+ return self._write_ws_title(ws, row_pos, ws_params)
+
+ def _empty_report(self, ws, row_pos, ws_params, data, wiz):
+ report = ws_params['report_type']
+ if report == 'acquisition':
+ suffix = _('New Acquisitions')
+ elif report == 'active':
+ suffix = _('Active Assets')
+ else:
+ suffix = _('Removed Assets')
+ no_entries = _("No") + " " + suffix
+ ws.write_string(row_pos, 0, no_entries, self.format_left_bold)
+
+ def _get_assets(self, wiz):
+
+ dom = [('date_start', '<=', wiz.date_to),
+ '|',
+ ('date_remove', '=', False),
+ ('date_remove', '>=', wiz.date_from)]
+
+ parent_group = wiz.asset_group_id
+ if parent_group:
+
+ def _child_get(parent):
+ groups = [parent]
+ children = parent.child_ids
+ children = children.sorted(lambda r: r.code or r.name)
+ for child in children:
+ if child in groups:
+ raise UserError(_(
+ "Inconsistent reporting structure."
+ "\nPlease correct Asset Group '%s' (id %s)"
+ ) % (child.name, child.id))
+ groups.extend(_child_get(child))
+ return groups
+
+ groups = _child_get(parent_group)
+ dom.append(('group_ids', 'in', [x.id for x in groups]))
+
+ if not wiz.draft:
+ dom.append(('state', '!=', 'draft'))
+ self._assets = self.env['account.asset'].search(dom)
+ grouped_assets = {}
+ self._group_assets(self._assets, parent_group, grouped_assets)
+ return grouped_assets
+
+ @staticmethod
+ def acquisition_filter(wiz, asset):
+ return asset.date_start >= wiz.date_from
+
+ @staticmethod
+ def active_filter(wiz, asset):
+ return True
+
+ @staticmethod
+ def removal_filter(wiz, asset):
+ return (
+ asset.date_remove and asset.date_remove >= wiz.date_from
+ and asset.date_remove <= wiz.date_to)
+
+ def _group_assets(self, assets, group, grouped_assets):
+ if group:
+ group_assets = assets.filtered(lambda r: group in r.group_ids)
+ else:
+ group_assets = assets
+ group_assets = group_assets.sorted(
+ lambda r: (r.date_start or '', r.code))
+ grouped_assets[group] = {'assets': group_assets}
+ for child in group.child_ids:
+ self._group_assets(assets, child, grouped_assets[group])
+
+ def _create_report_entries(self, ws_params, wiz, entries,
+ group, group_val, error_dict):
+ report = ws_params['report_type']
+
+ def asset_filter(asset):
+ filter = getattr(self, '{}_filter'.format(report))
+ return filter(wiz, asset)
+
+ def _has_assets(group, group_val):
+ assets = group_val.get('assets')
+ assets = assets.filtered(asset_filter)
+ if assets:
+ return True
+ for child in group.child_ids:
+ if _has_assets(child, group_val[child]):
+ return True
+ return False
+
+ assets = group_val.get('assets')
+ assets = assets.filtered(asset_filter)
+
+ # remove empty entries
+ if not _has_assets(group, group_val):
+ return
+
+ asset_entries = []
+ group._depreciation_base = 0.0
+ group._salvage_value = 0.0
+ group._period_start_value = 0.0
+ group._period_end_value = 0.0
+ for asset in assets:
+ group._depreciation_base += asset.depreciation_base
+ group._salvage_value += asset.salvage_value
+ dls_all = asset.depreciation_line_ids.filtered(
+ lambda r: r.type == 'depreciate')
+ dls_all = dls_all.sorted(key=lambda r: r.line_date)
+ if not dls_all:
+ error_dict['no_table'] += asset
+ # period_start_value
+ dls = dls_all.filtered(lambda r: r.line_date <= wiz.date_from)
+ if dls:
+ value_depreciated = dls[-1].depreciated_value + dls[-1].amount
+ else:
+ value_depreciated = 0.0
+ asset._period_start_value = \
+ asset.depreciation_base - value_depreciated
+ group._period_start_value += asset._period_start_value
+ # period_end_value
+ dls = dls_all.filtered(lambda r: r.line_date <= wiz.date_to)
+ if dls:
+ value_depreciated = dls[-1].depreciated_value + dls[-1].amount
+ else:
+ value_depreciated = 0.0
+ asset._period_end_value = \
+ asset.depreciation_base - value_depreciated
+ group._period_end_value += asset._period_end_value
+
+ asset_entries.append({'asset': asset})
+
+ todos = []
+ for g in group.child_ids:
+ if _has_assets(g, group_val[g]):
+ todos.append(g)
+
+ entries.append({'group': group})
+ entries.extend(asset_entries)
+ for todo in todos:
+ self._create_report_entries(ws_params, wiz, entries,
+ todo, group_val[todo], error_dict)
+
+ def _asset_report(self, workbook, ws, ws_params, data, wiz):
+ report = ws_params['report_type']
+
+ ws.set_portrait()
+ ws.fit_to_pages(1, 0)
+ ws.set_header(self.xls_headers['standard'])
+ ws.set_footer(self.xls_footers['standard'])
+
+ wl = ws_params['wanted_list']
+ if 'account' not in wl:
+ raise UserError(_(
+ "The 'account' field is a mandatory entry of the "
+ "'_xls_%s_fields' list !"
+ ) % report)
+
+ self._set_column_width(ws, ws_params)
+
+ row_pos = 0
+ row_pos = self._report_title(ws, row_pos, ws_params, data, wiz)
+
+ def asset_filter(asset):
+ filter = getattr(self, '{}_filter'.format(report))
+ return filter(wiz, asset)
+
+ assets = self._assets.filtered(asset_filter)
+
+ if not assets:
+ return self._empty_report(
+ ws, row_pos, ws_params, data, wiz)
+
+ row_pos = self._write_line(
+ ws, row_pos, ws_params, col_specs_section='header',
+ default_format=self.format_theader_yellow_left)
+
+ ws.freeze_panes(row_pos, 0)
+
+ row_pos_start = row_pos
+ depreciation_base_pos = 'depreciation_base' in wl and \
+ wl.index('depreciation_base')
+ salvage_value_pos = 'salvage_value' in wl and \
+ wl.index('salvage_value')
+ period_start_value_pos = 'period_start_value' in wl and \
+ wl.index('period_start_value')
+ period_end_value_pos = 'period_end_value' in wl and \
+ wl.index('period_end_value')
+
+ entries = []
+ root = wiz.asset_group_id
+ root_val = self._grouped_assets[root]
+ error_dict = {
+ 'no_table': self.env['account.asset'],
+ 'dups': self.env['account.asset'],
+ }
+
+ self._create_report_entries(ws_params, wiz, entries,
+ root, root_val, error_dict)
+
+ # traverse entries in reverse order to calc totals
+ for i, entry in enumerate(reversed(entries)):
+ group = entry.get('group')
+ if 'group' in entry:
+ parent = group.parent_id
+ for e in reversed(entries[:-i-1]):
+ g = e.get('group')
+ if g == parent:
+ g._depreciation_base += group._depreciation_base
+ g._salvage_value += group._salvage_value
+ g._period_start_value += group._period_start_value
+ g._period_end_value += group._period_end_value
+ continue
+
+ processed = []
+ for entry in entries:
+
+ period_start_value_cell = period_start_value_pos \
+ and self._rowcol_to_cell(row_pos, period_start_value_pos)
+ period_end_value_cell = period_end_value_pos \
+ and self._rowcol_to_cell(row_pos, period_end_value_pos)
+ depreciation_base_cell = depreciation_base_pos \
+ and self._rowcol_to_cell(row_pos, depreciation_base_pos)
+ period_diff_formula = period_end_value_cell and (
+ period_start_value_cell + '-' + period_end_value_cell)
+ total_depr_formula = period_end_value_cell and (
+ depreciation_base_cell + '-' + period_end_value_cell)
+
+ if 'group' in entry:
+ row_pos = self._write_line(
+ ws, row_pos, ws_params, col_specs_section='asset_group',
+ render_space={
+ 'group': entry['group'],
+ 'period_diff_formula': period_diff_formula,
+ 'total_depr_formula': total_depr_formula,
+ },
+ default_format=self.format_theader_blue_left)
+
+ else:
+ asset = entry['asset']
+ if asset in processed:
+ error_dict['dups'] += asset
+ continue
+ else:
+ processed.append(asset)
+ row_pos = self._write_line(
+ ws, row_pos, ws_params, col_specs_section='asset',
+ render_space={
+ 'asset': asset,
+ 'period_diff_formula': period_diff_formula,
+ 'total_depr_formula': total_depr_formula,
+ },
+ default_format=self.format_tcell_left)
+
+ asset_total_formula = depreciation_base_pos \
+ and self._rowcol_to_cell(row_pos_start, depreciation_base_pos)
+ salvage_total_formula = salvage_value_pos \
+ and self._rowcol_to_cell(row_pos_start, salvage_value_pos)
+ period_start_total_formula = period_start_value_pos \
+ and self._rowcol_to_cell(row_pos_start, period_start_value_pos)
+ period_end_total_formula = period_end_value_pos \
+ and self._rowcol_to_cell(row_pos_start, period_end_value_pos)
+ period_start_value_cell = period_start_value_pos \
+ and self._rowcol_to_cell(row_pos, period_start_value_pos)
+ period_end_value_cell = period_end_value_pos \
+ and self._rowcol_to_cell(row_pos, period_end_value_pos)
+ depreciation_base_cell = depreciation_base_pos \
+ and self._rowcol_to_cell(row_pos, depreciation_base_pos)
+ period_diff_formula = period_end_value_cell and (
+ period_start_value_cell + '-' + period_end_value_cell)
+ total_depr_formula = period_end_value_cell and (
+ depreciation_base_cell + '-' + period_end_value_cell)
+
+ row_pos = self._write_line(
+ ws, row_pos, ws_params, col_specs_section='totals',
+ render_space={
+ 'asset_total_formula': asset_total_formula,
+ 'salvage_total_formula': salvage_total_formula,
+ 'period_start_total_formula': period_start_total_formula,
+ 'period_end_total_formula': period_end_total_formula,
+ 'period_diff_formula': period_diff_formula,
+ 'total_depr_formula': total_depr_formula,
+ },
+ default_format=self.format_theader_yellow_left)
+
+ for k in error_dict:
+ if error_dict[k]:
+ if k == 'no_table':
+ reason = _("Missing depreciation table")
+ elif k == 'dups':
+ reason = _("Duplicate reporting entries")
+ else:
+ reason = _("Undetermined error")
+ row_pos += 1
+ err_msg = _("Assets to be corrected") + ': '
+ err_msg += '%s' % [x[1] for x in error_dict[k].name_get()]
+ err_msg += ' - ' + _("Reason") + ': ' + reason
+ ws.write_string(row_pos, 0, err_msg, self.format_left_bold)
diff --git a/account_asset_management/tests/__init__.py b/account_asset_management/tests/__init__.py
index abadecb7f..8187a598e 100644
--- a/account_asset_management/tests/__init__.py
+++ b/account_asset_management/tests/__init__.py
@@ -1 +1,2 @@
from . import test_account_asset_management
+from . import test_asset_management_xls
diff --git a/account_asset_management/tests/account_asset_test_data.xml b/account_asset_management/tests/account_asset_test_data.xml
index c6883f166..89e310ce8 100644
--- a/account_asset_management/tests/account_asset_test_data.xml
+++ b/account_asset_management/tests/account_asset_test_data.xml
@@ -16,6 +16,19 @@
+
+
+
+ Fixed Assets
+ FA
+
+
+
+ Tangible Fixed Assets
+ TFA
+
+
+
@@ -52,6 +65,7 @@
PI00101
+
diff --git a/account_asset_management/tests/test_asset_management_xls.py b/account_asset_management/tests/test_asset_management_xls.py
new file mode 100644
index 000000000..ac4cb0f4b
--- /dev/null
+++ b/account_asset_management/tests/test_asset_management_xls.py
@@ -0,0 +1,47 @@
+# Copyright 2009-2019 Noviat.
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo.tests.common import SavepointCase
+from odoo import fields, tools
+from odoo.modules.module import get_resource_path
+
+
+class TestAssetManagementXls(SavepointCase):
+
+ @classmethod
+ def _load(cls, module, *args):
+ tools.convert_file(cls.cr, module,
+ get_resource_path(module, *args),
+ {}, 'init', False, 'test',
+ cls.registry._assertion_report)
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestAssetManagementXls, cls).setUpClass()
+
+ cls._load('account', 'test', 'account_minimal_test.xml')
+ cls._load('account_asset_management', 'tests',
+ 'account_asset_test_data.xml')
+ module = __name__.split('addons.')[1].split('.')[0]
+ cls.xls_report_name = '{}.asset_report_xls'.format(module)
+ cls.wiz_model = cls.env['wiz.account.asset.report']
+ cls.company = cls.env.ref('base.main_company')
+ asset_group_id = cls.wiz_model._default_asset_group_id()
+ fy_dates = cls.company.compute_fiscalyear_dates(
+ fields.date.today())
+
+ wiz_vals = {
+ 'asset_group_id': asset_group_id,
+ 'date_from': fy_dates['date_from'],
+ 'date_to': fy_dates['date_to'],
+ }
+ cls.xls_report = cls.wiz_model.create(wiz_vals)
+ cls.report_action = cls.xls_report.xls_export()
+
+ def test_01_action_xls(self):
+ """ Check report XLS action """
+ self.assertDictContainsSubset(
+ {'type': 'ir.actions.report',
+ 'report_type': 'xlsx',
+ 'report_name': self.xls_report_name},
+ self.report_action)
diff --git a/account_asset_management/views/account_asset.xml b/account_asset_management/views/account_asset.xml
index e9b7f3d4d..8bc1b8605 100644
--- a/account_asset_management/views/account_asset.xml
+++ b/account_asset_management/views/account_asset.xml
@@ -179,6 +179,7 @@
+
@@ -198,6 +199,7 @@
+
diff --git a/account_asset_management/wizard/__init__.py b/account_asset_management/wizard/__init__.py
index 475dc4e72..a9ffac035 100644
--- a/account_asset_management/wizard/__init__.py
+++ b/account_asset_management/wizard/__init__.py
@@ -1,2 +1,3 @@
from . import account_asset_compute
from . import account_asset_remove
+from . import wiz_account_asset_report
diff --git a/account_asset_management/wizard/wiz_account_asset_report.py b/account_asset_management/wizard/wiz_account_asset_report.py
new file mode 100644
index 000000000..51df58b9c
--- /dev/null
+++ b/account_asset_management/wizard/wiz_account_asset_report.py
@@ -0,0 +1,74 @@
+# Copyright 2009-2019 Noviat
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+import unicodedata
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+
+
+class WizAccountAssetReport(models.TransientModel):
+ _name = 'wiz.account.asset.report'
+ _description = 'Financial Assets report'
+
+ asset_group_id = fields.Many2one(
+ comodel_name='account.asset.group',
+ string='Asset Group',
+ default=lambda self: self._default_asset_group_id())
+ date_from = fields.Date(
+ string='Start Date',
+ required=True)
+ date_to = fields.Date(
+ string='End Date',
+ required=True)
+ draft = fields.Boolean(
+ string='Include draft assets')
+ company_id = fields.Many2one(
+ comodel_name='res.company',
+ string='Company', required=True,
+ default=lambda self: self._default_company_id())
+
+ @api.model
+ def _default_asset_group_id(self):
+ return self.env['account.asset.group'].search([
+ ('parent_id', '=', False)], limit=1).id
+
+ @api.model
+ def _default_company_id(self):
+ return self.env.user.company_id
+
+ @api.onchange('company_id')
+ def _onchange_company_id(self):
+ fy_dates = self.company_id.compute_fiscalyear_dates(
+ fields.date.today())
+ self.date_from = fy_dates['date_from']
+ self.date_to = fy_dates['date_to']
+
+ @api.multi
+ @api.constrains('date_from', 'date_to')
+ def _check_dates(self):
+ for wiz in self:
+ if wiz.date_to <= wiz.date_from:
+ raise UserError(_(
+ "The Start Date must precede the Ending Date."))
+
+ @api.multi
+ def xls_export(self):
+ self.ensure_one()
+ report_name = 'account_asset_management.asset_report_xls'
+ if self.asset_group_id:
+ prefix = unicodedata.normalize(
+ 'NFKD', self.asset_group_id.name
+ ).encode('ascii', 'ignore').decode('ascii')
+ prefix = ''.join(x for x in prefix if x.isalnum())
+ report_file = '{}_asset_report'.format(prefix)
+ else:
+ report_file = 'asset_report'
+ report = {
+ 'type': 'ir.actions.report',
+ 'report_type': 'xlsx',
+ 'report_name': report_name,
+ 'context': dict(self.env.context, report_file=report_file),
+ 'data': {'dynamic_report': True},
+ }
+ return report
diff --git a/account_asset_management/wizard/wiz_account_asset_report.xml b/account_asset_management/wizard/wiz_account_asset_report.xml
new file mode 100644
index 000000000..180c450d1
--- /dev/null
+++ b/account_asset_management/wizard/wiz_account_asset_report.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ Financial Assets report
+ wiz.account.asset.report
+
+
+
+
+
+
+ Financial Assets report
+ ir.actions.act_window
+ wiz.account.asset.report
+ form
+ form
+
+ new
+
+
+
+
+
+
+
diff --git a/oca_dependencies.txt b/oca_dependencies.txt
index 2751ff926..c3abe3c25 100644
--- a/oca_dependencies.txt
+++ b/oca_dependencies.txt
@@ -1,3 +1,4 @@
queue
+reporting-engine
server-ux
server-tools