2
0

[IMP] account_asset_management: Add reporting

This commit is contained in:
Luc De Meyer 2019-10-06 23:24:00 +02:00 committed by Rodrigo
parent 0d332ba006
commit e4796d3bd5
14 changed files with 987 additions and 6 deletions

View File

@ -1,2 +1,3 @@
from . import models from . import models
from . import report
from . import wizard from . import wizard

View File

@ -1,4 +1,4 @@
# Copyright 2009-2018 Noviat # Copyright 2009-2019 Noviat
# Copyright 2019 Tecnativa - Pedro M. Baeza # Copyright 2019 Tecnativa - Pedro M. Baeza
# Copyright 2021 Tecnativa - João Marques # Copyright 2021 Tecnativa - João Marques
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@ -7,7 +7,7 @@
"name": "Assets Management", "name": "Assets Management",
"version": "14.0.1.0.4", "version": "14.0.1.0.4",
"license": "AGPL-3", "license": "AGPL-3",
"depends": ["account"], "depends": ["account", "report_xlsx_helper"],
"excludes": ["account_asset"], "excludes": ["account_asset"],
"external_dependencies": {"python": ["python-dateutil"]}, "external_dependencies": {"python": ["python-dateutil"]},
"author": "Noviat, Odoo Community Association (OCA)", "author": "Noviat, Odoo Community Association (OCA)",
@ -26,5 +26,6 @@
"views/account_move_line.xml", "views/account_move_line.xml",
"views/menuitem.xml", "views/menuitem.xml",
"data/cron.xml", "data/cron.xml",
"wizard/wiz_account_asset_report.xml",
], ],
} }

View File

@ -1202,3 +1202,85 @@ class AccountAsset(models.Model):
triggers.sudo().write(recompute_vals) triggers.sudo().write(recompute_vals)
return (result, error_log) 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 # Copyright 2019 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models from odoo import api, fields, models
from odoo.osv import expression
class AccountAssetGroup(models.Model): class AccountAssetGroup(models.Model):
_name = "account.asset.group" _name = "account.asset.group"
_description = "Asset Group" _description = "Asset Group"
_order = "name" _order = "code, name"
_parent_store = True _parent_store = True
name = fields.Char(string="Name", size=64, required=True, index=True) name = fields.Char(string="Name", size=64, required=True, index=True)
@ -25,7 +26,53 @@ class AccountAssetGroup(models.Model):
string="Parent Asset Group", string="Parent Asset Group",
ondelete="restrict", ondelete="restrict",
) )
child_ids = fields.One2many(
comodel_name="account.asset.group",
inverse_name="parent_id",
string="Child Asset Groups",
)
@api.model @api.model
def _default_company_id(self): def _default_company_id(self):
return self.env.company return self.env.company
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

@ -15,7 +15,5 @@ or automatically by two ways:
These options are compatibles each other. These options are compatibles each other.
Excel based reporting is available via the 'account_asset_management_xls' module.
The module contains a large number of functional enhancements compared to The module contains a large number of functional enhancements compared to
the standard account_asset module from Odoo. the standard account_asset module from Odoo.

View File

@ -0,0 +1 @@
from . import account_asset_report_xls

View File

@ -0,0 +1,684 @@
# 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"
_description = "Dynamic XLS asset report generator"
_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

@ -15,3 +15,4 @@ access_account_asset_group_user,account.asset.group,model_account_asset_group,ac
access_account_asset_group_manager,account.asset.group,model_account_asset_group,account.group_account_manager,1,1,1,1 access_account_asset_group_manager,account.asset.group,model_account_asset_group,account.group_account_manager,1,1,1,1
access_account_asset_remove_user,account.asset.remove,model_account_asset_remove,account.group_account_user,1,1,1,1 access_account_asset_remove_user,account.asset.remove,model_account_asset_remove,account.group_account_user,1,1,1,1
access_account_asset_compute_user,account.asset.compute,model_account_asset_compute,account.group_account_user,1,1,1,1 access_account_asset_compute_user,account.asset.compute,model_account_asset_compute,account.group_account_user,1,1,1,1
access_wiz_account_asset_report,wiz.account.asset.report,model_wiz_account_asset_report,account.group_account_readonly,1,1,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
15 access_account_asset_group_manager account.asset.group model_account_asset_group account.group_account_manager 1 1 1 1
16 access_account_asset_remove_user account.asset.remove model_account_asset_remove account.group_account_user 1 1 1 1
17 access_account_asset_compute_user account.asset.compute model_account_asset_compute account.group_account_user 1 1 1 1
18 access_wiz_account_asset_report wiz.account.asset.report model_wiz_account_asset_report account.group_account_readonly 1 1 1 0

View File

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

View File

@ -0,0 +1,37 @@
# Copyright 2009-2019 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo.tests.common import SavepointCase
class TestAssetManagementXls(SavepointCase):
@classmethod
def setUpClass(cls):
super(TestAssetManagementXls, cls).setUpClass()
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.assertGreaterEqual(
self.report_action.items(),
{
"type": "ir.actions.report",
"report_type": "xlsx",
"report_name": self.xls_report_name,
}.items(),
)

View File

@ -315,6 +315,7 @@
<field name="date_remove" /> <field name="date_remove" />
<field name="profile_id" /> <field name="profile_id" />
<field name="state" /> <field name="state" />
<field name="group_ids" widget="many2many_tags" />
<field name="company_id" groups="base.group_multi_company" /> <field name="company_id" groups="base.group_multi_company" />
</tree> </tree>
</field> </field>
@ -353,6 +354,7 @@
<field name="code" /> <field name="code" />
<field name="date_start" /> <field name="date_start" />
<field name="profile_id" /> <field name="profile_id" />
<field name="group_ids" />
<field <field
name="partner_id" name="partner_id"
filter_domain="[('partner_id', 'child_of', self)]" filter_domain="[('partner_id', 'child_of', self)]"

View File

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

View File

@ -0,0 +1,73 @@
# 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.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."))
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,52 @@
<?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_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>