Files
enterprise-addons/esg/models/carbon_report.py
T

270 lines
13 KiB
Python

from odoo import models
from odoo.tools import SQL
class CarbonReportHandler(models.AbstractModel):
_name = 'esg.carbon.report.handler'
_inherit = ['account.report.custom.handler']
_description = 'Carbon Report Custom Handler'
def _custom_options_initializer(self, report, options, previous_options):
super()._custom_options_initializer(report, options, previous_options=previous_options)
options['float_rounding'] = 2
selected_gas = {
gas_option.get('code') for gas_option in previous_options.get('carbon_report_selected_gas', [])
if gas_option.get('selected') and gas_option.get('code')
}
options['carbon_report_selected_gas'] = [
{'name': gas.name, 'id': gas.id, 'selected': gas.code in selected_gas, 'code': gas.code}
for gas in self.env['esg.gas'].sudo().search([])
]
options['columns'] = [col_opt for col_opt in options['columns'] if col_opt['expression_label'] in {*selected_gas, 'co2e'}]
options['custom_display_config'] = {
'templates': {
'AccountReportFilters': 'esg.CarbonReportFilters',
}
}
def _report_custom_engine_carbon_report(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
report = self.env['account.report'].browse(options['report_id'])
report._check_groupby_fields((next_groupby.split(',') if next_groupby else []) + ([current_groupby] if current_groupby else []))
query = report._get_report_query(options, date_scope)
groupby_sql = ''
# Handle custom groupby values supported by this report
if current_groupby == 'carbon_report_source_hierarchy':
groupby_sql = SQL('esg_emission_source.id')
elif current_groupby == 'carbon_report_source_scope':
groupby_sql = SQL('esg_emission_source.scope')
elif current_groupby:
groupby_sql = self.env['account.move.line']._field_to_sql('account_move_line', current_groupby, query)
stored_aml_fields, emission_shadowing_fields_to_insert = self.env['account.move.line']._prepare_aml_shadowing_for_report({
'date': SQL.identifier('esg_other_emission', 'date'),
'esg_emission_factor_id': SQL.identifier('esg_other_emission', 'esg_emission_factor_id'),
'quantity': SQL.identifier('esg_other_emission', 'quantity'),
'company_id': SQL.identifier('esg_other_emission', 'company_id'),
'esg_emission_multiplicator': SQL.identifier('esg_other_emission', 'esg_emission_multiplicator'),
'parent_state': 'posted',
'display_type': 'product',
}, prefix_fields=True)
filtered_gas_select = SQL(', ').join(
SQL(
"""
COALESCE(
SUM(
CASE WHEN esg_emission_factor_line.gas_id = %(gas_id)s
THEN esg_emission_factor_line.quantity * account_move_line.esg_emission_multiplicator
ELSE 0
END
) / 1000, -- /1000 to express it in tons instad of kg
0
) AS %(gas_label)s
""",
gas_id=gas_opt['id'],
gas_label=SQL.identifier(gas_opt['code']),
)
for gas_opt in options['carbon_report_selected_gas']
)
report_sql = SQL(
"""
SELECT
%(filtered_gas_select)s
COALESCE(
SUM(
CASE WHEN esg_emission_factor_line.id IS NULL THEN
-- For emission factors without any gas line
esg_emission_factor.esg_emissions_value * account_move_line.esg_emission_multiplicator
ELSE
esg_emission_factor_line.esg_emissions_value * account_move_line.esg_emission_multiplicator
END
),
0
) / 1000 AS co2e
-- /1000 to express it in tons instad of kg
%(select_groupby_sql)s
FROM (
-- Local shadowing ; we extend the account_move_line table artificially
SELECT %(stored_aml_fields)s
FROM %(table_references)s
WHERE account_id IN (
SELECT id
FROM account_account
WHERE account_type IN %(valid_account_types)s
)
UNION ALL
SELECT %(emission_shadowing_fields_to_insert)s
FROM esg_other_emission
JOIN esg_emission_factor other_emission_factor
ON other_emission_factor.id = esg_other_emission.esg_emission_factor_id
) AS %(table_references)s
JOIN esg_emission_factor
ON esg_emission_factor.id = account_move_line.esg_emission_factor_id
JOIN esg_emission_source
ON esg_emission_source.id = esg_emission_factor.source_id
LEFT JOIN esg_emission_factor_line
ON esg_emission_factor_line.esg_emission_factor_id = esg_emission_factor.id
WHERE
%(search_condition)s
%(group_by_groupby_sql)s
%(order_by_sql)s
%(query_tail)s
""",
filtered_gas_select=SQL("%s, ", filtered_gas_select) if filtered_gas_select else SQL(),
select_groupby_sql=SQL(', %s AS grouping_key', groupby_sql) if groupby_sql else SQL(),
table_references=query.from_clause,
stored_aml_fields=stored_aml_fields,
valid_account_types=self.env['account.account'].ESG_VALID_ACCOUNT_TYPES,
emission_shadowing_fields_to_insert=emission_shadowing_fields_to_insert,
search_condition=query.where_clause,
group_by_groupby_sql=SQL('GROUP BY %s', groupby_sql) if groupby_sql else SQL(),
order_by_sql=SQL(' ORDER BY %s', groupby_sql) if groupby_sql else SQL(),
query_tail=report._get_engine_query_tail(offset, limit),
)
self.env.cr.execute(report_sql)
query_res_lines = self.env.cr.dictfetchall()
gas_labels = [gas_opt['code'] for gas_opt in options['carbon_report_selected_gas']] + ['co2e']
if not current_groupby:
return {gas_label: query_res_lines[0][gas_label] for gas_label in gas_labels}
elif current_groupby == 'carbon_report_source_hierarchy':
source_parents = {
source.id: source.parent_id.id or None
for source in self.env['esg.emission.source'].sudo().search([])
}
highest_emission_source_in_hierarchy = self.env.context.get('highest_emission_source_in_hierarchy')
totals_including_child_sources = {} # in the form {source_id: {gas: value}}
sources_with_sublines = set()
for query_res in query_res_lines:
# Find the highest parent that can be displayed ; it will receive the value of all its children
source_id = query_res['grouping_key']
while (source_parent := source_parents.get(source_id)) and source_parent != highest_emission_source_in_hierarchy:
source_id = source_parent
if source_parent == highest_emission_source_in_hierarchy:
source_totals = totals_including_child_sources.setdefault(source_id, {})
for gas_label in gas_labels:
source_totals.setdefault(gas_label, 0)
source_totals[gas_label] += query_res[gas_label]
if source_id != query_res['grouping_key']:
sources_with_sublines.add(source_id)
return [
(source_id, {**source_totals, 'has_sublines': source_id in sources_with_sublines})
for source_id, source_totals in totals_including_child_sources.items()
]
else:
return [
(query_res['grouping_key'], {**{gas_label: query_res[gas_label] for gas_label in gas_labels}, 'has_sublines': True})
for query_res in query_res_lines
]
def _custom_groupby_line_completer(self, report, options, line_dict, current_groupby):
if current_groupby == 'carbon_report_source_hierarchy':
if line_dict.get('unfoldable'):
esg_source_id = report._get_model_info_from_id(line_dict['id'])[1]
esg_source = self.env['esg.emission.source'].browse(esg_source_id)
if esg_source.child_ids:
line_dict.update({
'groupby': f"carbon_report_source_hierarchy,{line_dict['groupby']}",
'expand_function': '_report_expand_unfoldable_line_with_groupby_emission_source',
})
elif line_dict.get('groupby'):
# This will happen when has_sublines gives a falsy value in the custom engine.
# In this case, if there is still a level of grouping, we stop the hierarchy cycling.
# We then need to make the line unfoldable again, to allow proceeding to this next groupby level.
line_dict['unfoldable'] = True
if current_groupby in ('carbon_report_source_scope', 'carbon_report_source_hierarchy', 'esg_emission_factor_id'):
for col_dict in filter(lambda x: x['expression_label'] == 'co2e', line_dict['columns']):
col_dict['auditable'] = True
def _get_custom_groupby_map(self):
def carbon_report_source_hierarchy_label_builder(grouping_keys):
keys_and_names_in_sequence = {}
for emission_source in self.env['esg.emission.source'].browse(grouping_keys):
keys_and_names_in_sequence[emission_source.id] = emission_source.name
return keys_and_names_in_sequence
def carbon_report_source_scope_label_builder(grouping_keys):
keys_and_names_in_sequence = {}
labels_dict = dict(self.env['esg.emission.source']._fields['scope']._description_selection(self.env))
for grouping_key in grouping_keys:
keys_and_names_in_sequence[grouping_key] = labels_dict[grouping_key]
return keys_and_names_in_sequence
return {
'carbon_report_source_hierarchy': {
'model': 'esg.emission.source',
'domain_builder': lambda source_id: [('esg_emission_factor_id.source_id', 'child_of', source_id)],
'label_builder': carbon_report_source_hierarchy_label_builder,
},
'carbon_report_source_scope': {
'model': None,
'domain_builder': lambda scope: [('esg_emission_factor_id.source_id.scope', '=', scope)],
'label_builder': carbon_report_source_scope_label_builder,
},
}
def _report_expand_unfoldable_line_with_groupby_emission_source(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None):
# esg.emission_source model defines a recursive hierarchy of records. This custom groupby behaves differently
# than the traditional groupby used by reports, in that it will not always correspond to a single level of lines.
# To handle that, we use this custom expand function, which uses a specific context key to limit the next unfolding, and restores
# the same groupby on the next level if needed.
report = self.env['account.report'].browse(options['report_id'])
unfolded_source_id = report._get_res_id_from_line_id(line_dict_id, 'esg.emission.source')
if unfolded_source_id:
report = report.with_context(highest_emission_source_in_hierarchy=unfolded_source_id)
return report._report_expand_unfoldable_line_with_groupby(
line_dict_id,
groupby,
options,
progress,
offset,
unfold_all_batch_data=unfold_all_batch_data,
)
def action_audit_cell(self, options, params):
report = self.env['account.report'].browse(options['report_id'])
parsed_line_id = report._parse_line_id(params['calling_line_dict_id'])
markup, _dummy, value = parsed_line_id[-1]
if markup == 'total':
markup, _dummy, value = parsed_line_id[-2]
domain = [('date', '>=', options['date']['date_from']), ('date', '<=', options['date']['date_to'])]
if markup.get('groupby') == 'carbon_report_source_scope':
domain += [('scope', '=', value)]
elif markup.get('groupby') == 'carbon_report_source_hierarchy':
domain += [('source_id', 'child_of', value)]
elif markup.get('groupby') == 'esg_emission_factor_id':
domain += [('esg_emission_factor_id', '=', value)]
# Other groupings aren't auditable
return {
'name': self.env._("Audit Emissions"),
'type': 'ir.actions.act_window',
'res_model': 'esg.carbon.emission.report',
'view_mode': 'list',
'views': [(False, 'list'), (False, 'form')],
'domain': domain,
}