Files
enterprise-addons/esg_csrd/models/esg_report.py
T

224 lines
9.0 KiB
Python

import json
from lxml import html
from odoo import api, fields, models, Command
from odoo.exceptions import UserError, ValidationError
class EsgReport(models.Model):
_name = 'esg.report'
_description = 'ESG Report'
def _get_report_type_selection(self):
selection = [
('vsme_basic', 'VSME - Basic Module'),
('vsme_advanced', 'VSME - Basic Module + Comprehensive Module'),
]
if self.env.user.has_group('esg_csrd.group_esg_csrd_reporting'):
selection.append(('csrd', 'CSRD'))
return selection
def _default_base_year(self):
last_year_report = self.search([], order='create_date desc', limit=1)
return last_year_report.base_year if last_year_report else fields.Date.today().year - 1
report_type = fields.Selection(
selection=lambda self: self._get_report_type_selection(),
required=True,
)
color = fields.Integer(export_string_translation=False)
knowledge_article_id = fields.Many2one(
'knowledge.article',
required=True,
index=True,
)
title = fields.Char(required=True)
start_date = fields.Date(
string='Reporting Date',
compute='_compute_dates',
store=True,
readonly=False,
required=True,
)
end_date = fields.Date(
string='Reporting End Date',
compute='_compute_dates',
store=True,
readonly=False,
required=True,
)
responsible_user_ids = fields.Many2many('res.users', string='Responsibles', default=lambda self: self.env.user)
status = fields.Selection(
string='Status',
selection=[('draft', 'Draft'), ('done', 'Done')],
default='draft',
)
company_id = fields.Many2one(
'res.company',
'Group Reporting Company',
default=lambda self: self.env['res.company'].sudo().ESG_REPORT_DEFAULT_COMPANY or self.env.company,
help='This company defines the fiscal year and main company information used for the report. If your group has several companies, choose the one representing the consolidated perimeter.',
required=True,
)
nace_id = fields.Many2one(
'esg.nace',
string='NACE Code',
help='Select the NACE code representing the company\'s main economic activity',
default=lambda self: self.search([], order='create_date desc', limit=1).nace_id,
)
base_year = fields.Integer(
help='''Select the histrocial reference year for tracking progress on metrics like GHG emissions, energy or water.
It serves as a benchmark for future comparisons and may be adjusted if major structural changes occur.
''',
default=_default_base_year
)
@api.depends('company_id')
def _compute_dates(self):
for report in self:
dates = report.company_id.sudo().compute_fiscalyear_dates(fields.Date.today())
report.start_date = dates['date_from']
report.end_date = dates['date_to']
@api.constrains('base_year')
def _check_base_year(self):
for report in self:
if report.base_year and not report._has_valid_base_year():
raise ValidationError(self.env._('The base year is not valid. It must be between 1000 and 9999.'))
@api.model_create_multi
def create(self, vals_list):
root_articles = self.env['knowledge.article'].create([{
'internal_permission': 'none',
'article_member_ids': [Command.create({
'partner_id': self.env.user.partner_id.id,
'permission': 'write',
})]
} for _ in vals_list])
for vals, root_article in zip(vals_list, root_articles):
vals['knowledge_article_id'] = root_article.id
esg_reports = super().create(vals_list)
esg_report_per_type = {}
for esg_report, root_article in zip(esg_reports, root_articles):
if esg_report.report_type == 'csrd':
if not (root_template := esg_report_per_type.get('csrd')):
root_template = self.env.ref('esg_csrd.esg_csrd_knowledge_article_template_csrd_report')
esg_report_per_type['csrd'] = root_template
else:
if not (root_template := esg_report_per_type.get('vsme')):
root_template = self.env.ref('esg_csrd.esg_csrd_knowledge_article_template_vsme_report')
esg_report_per_type['vsme'] = root_template
root_article.apply_template(root_template.id)
root_article.write({
'name': esg_report.title
})
# Invite the responsible users:
root_article.invite_members(esg_report.responsible_user_ids.partner_id, 'write')
return esg_reports
def write(self, vals):
if vals.get('responsible_user_ids'):
user_ids = [command[1] for command in vals['responsible_user_ids'] if command[0] == Command.LINK]
users = self.env['res.users'].browse(user_ids)
for article in self.knowledge_article_id:
article.invite_members(users.partner_id, 'write')
return super().write(vals)
def action_set_to_draft(self):
self.ensure_one()
self.status = 'draft'
def action_set_to_done(self):
self.ensure_one()
self.status = 'done'
def action_edit_esg_report(self):
self.ensure_one()
action = self.env['ir.actions.act_window']._for_xml_id('esg_csrd.action_esg_report_quick_create')
action['name'] = self.env._('Edit ESG Report')
action['res_id'] = self.id
action['context'] = {'default_report_type': self.report_type}
return action
def action_esg_report_pdf(self):
self.ensure_one()
return {
'type': 'ir.actions.act_url',
'url': f'/esg_csrd/article/{self.knowledge_article_id.id}/esg_report?include_pdf_files=true&include_child_articles=true',
'target': 'download'
}
def action_update_materiality(self):
self.ensure_one()
if self.report_type != 'csrd':
raise UserError(self.env._('You can only update the materiality of a CSRD report.'))
material_metric_codes = set(
self.env['esg.metric'].search_fetch(
domain=[
('date_start', '<=', self.end_date),
('date_end', '>=', self.start_date),
('materiality_type', '!=', 'non_material'),
],
field_names=['esrs_code'],
).mapped('esrs_code')
)
root_article = self.knowledge_article_id
stack = [root_article]
while stack:
article = stack.pop()
fragment = html.fragment_fromstring(article.body, create_parent='div')
is_section_updated = False
for element in fragment.xpath('//*[@data-embedded-props]'):
embedded_props = json.loads(element.get('data-embedded-props'))
if esrs_code := embedded_props.get('esrs_code'):
embedded_props = json.loads(element.get('data-embedded-props'))
is_esrs_material = esrs_code in material_metric_codes
show_content = is_esrs_material == embedded_props.get('is_esrs_material')
embedded_props['showContent'] = show_content
element.set('data-embedded-props', json.dumps(embedded_props))
current_class = element.get('class', '')
if show_content:
# Remove 'd-print-none' if present
element.set('class', current_class.replace('d-print-none', ''))
else:
# Add 'd-print-none' if not already present
if 'd-print-none' not in current_class:
element.set('class', current_class + ' d-print-none')
is_section_updated = True
if is_section_updated:
elements = []
for child in fragment.getchildren():
elements.append(html.tostring(child, encoding='unicode', method='html'))
article.write({'body': ''.join(elements)})
stack.extend(article.child_ids)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': self.env._(
'The sections of %(report_name)s have been succesfully updated with the latest materiality results.',
report_name=self.title,
),
'next': {'type': 'ir.actions.act_window_close'},
}
}
def action_open_knowledge_report_article(self):
self.ensure_one()
action = self.env['ir.actions.actions']._for_xml_id('knowledge.ir_actions_server_knowledge_home_page')
action['context'] = {'res_id': self.knowledge_article_id.id}
return action
def _has_valid_base_year(self):
self.ensure_one()
return self.base_year >= 1000 and self.base_year <= 9999