224 lines
9.0 KiB
Python
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
|