[IMP] account_asset_management: Add reporting

This commit is contained in:
Luc De Meyer
2019-10-06 23:24:00 +02:00
committed by Stefan Rijnhart
parent 43b7c30e54
commit 4ee1901ce1
14 changed files with 976 additions and 5 deletions

View File

@@ -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',
],
}

View File

@@ -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 {}

View File

@@ -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()

View File

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

View File

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

View File

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

View File

@@ -1 +1,2 @@
from . import test_account_asset_management
from . import test_asset_management_xls

View File

@@ -16,6 +16,19 @@
<field name="company_id" ref="base.main_company"/>
</record>
<!-- Asset groups -->
<record id="account_asset_group_fa" model="account.asset.group">
<field name="name">Fixed Assets</field>
<field name="code">FA</field>
</record>
<record id="account_asset_group_tfa" model="account.asset.group">
<field name="name">Tangible Fixed Assets</field>
<field name="code">TFA</field>
<field name="parent_id" ref="account_asset_group_fa"/>
</record>
<!-- Asset profiles -->
<record id="account_asset_profile_ict_3Y" model="account.asset.profile">
@@ -52,6 +65,7 @@
<field name="code">PI00101</field>
<field name="purchase_value" eval="1500.0"/>
<field name="profile_id" ref="account_asset_profile_ict_3Y"/>
<field name="group_ids" eval="[(4, ref('account_asset_group_tfa'))]"/>
</record>
<record id="account_asset_asset_vehicle0" model="account.asset">

View File

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

View File

@@ -179,6 +179,7 @@
<field name="date_remove"/>
<field name="profile_id"/>
<field name="state"/>
<field name="group_ids" widget="many2many_tags"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
@@ -198,6 +199,7 @@
<field name="code"/>
<field name="date_start"/>
<field name="profile_id"/>
<field name="group_ids"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<group expand="0" string="Group By...">
<filter string="Profile" name="profile" domain="" context="{'group_by': 'profile_id'}"/>

View File

@@ -1,2 +1,3 @@
from . import account_asset_compute
from . import account_asset_remove
from . import wiz_account_asset_report

View File

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

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="wiz_account_asset_report_view_form" model="ir.ui.view">
<field name="name">Financial Assets report</field>
<field name="model">wiz.account.asset.report</field>
<field name="arch" type="xml">
<form string="Financial Assets report">
<group col="2" colspan="4">
<field name="asset_group_id" options="{'no_create_edit': True, 'no_create': True}"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="draft"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<footer>
<button name="xls_export" string="Generate Report" type="object"
default_focus="1" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="wiz_account_asset_report_action" model="ir.actions.act_window">
<field name="name">Financial Assets report</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">wiz.account.asset.report</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="wiz_account_asset_report_view_form"/>
<field name="target">new</field>
</record>
<menuitem id="account_asset_report_menu"
name="Financial Assets"
parent="account.menu_finance_reports"/>
<menuitem id="wiz_account_asset_report_menu"
name="Financial Assets report"
parent="account_asset_report_menu"
action="wiz_account_asset_report_action"
sequence="200"/>
</odoo>

View File

@@ -1,3 +1,4 @@
queue
reporting-engine
server-ux
server-tools